BuildAPI: Add unit tests and mocks for Artifacts service.

Also added unit tests for FetchPinnedGuestImages.

BUG=chromium:1000845
TEST=run_tests

Change-Id: Ib8d44b03c0ac849055fb8330fef72b2737efce2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/1937729
Reviewed-by: Alex Klein <saklein@chromium.org>
Tested-by: Michael Mortensen <mmortensen@google.com>
Commit-Queue: Nicolas Boichat <drinkcat@chromium.org>
diff --git a/api/controller/artifacts_unittest.py b/api/controller/artifacts_unittest.py
index d11a595..3dc2271 100644
--- a/api/controller/artifacts_unittest.py
+++ b/api/controller/artifacts_unittest.py
@@ -7,6 +7,7 @@
 
 from __future__ import print_function
 
+import collections
 import os
 
 import mock
@@ -25,6 +26,10 @@
 from chromite.service import artifacts as artifacts_svc
 
 
+PinnedGuestImage = collections.namedtuple('PinnedGuestImage',
+                                          ['filename', 'uri'])
+
+
 class BundleRequestMixin(object):
   """Mixin to provide bundle request methods."""
 
@@ -106,6 +111,18 @@
                                   self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'ArchiveImages')
+    artifacts.BundleImageArchives(self.target_request, self.response,
+                                  self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 2)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'path0.tar.xz'))
+    self.assertEqual(self.response.artifacts[1].path,
+                     os.path.join(self.output_dir, 'path1.tar.xz'))
+
   def testNoBuildTarget(self):
     """Test that no build target fails."""
     request = self.BuildTargetRequest(output_dir=self.tempdir)
@@ -147,6 +164,16 @@
                              self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(commands, 'BuildImageZip')
+    artifacts.BundleImageZip(self.target_request, self.response,
+                             self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'image.zip'))
+
   def testBundleImageZip(self):
     """BundleImageZip calls cbuildbot/commands with correct args."""
     bundle_image_zip = self.PatchObject(
@@ -181,6 +208,16 @@
                                   self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleAutotestFiles')
+    artifacts.BundleAutotestFiles(self.target_request, self.response,
+                                  self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'autotest-a.tar.gz'))
+
   def testBundleAutotestFilesLegacy(self):
     """BundleAutotestFiles calls service correctly with legacy args."""
     files = {
@@ -257,6 +294,16 @@
                               self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleTastFiles')
+    artifacts.BundleTastFiles(self.target_request, self.response,
+                              self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'tast_bundles.tar.gz'))
+
   def testBundleTastFilesNoLogs(self):
     """BundleTasteFiles dies when no tast files found."""
     self.PatchObject(commands, 'BuildTastBundleTarball',
@@ -322,6 +369,17 @@
                                       self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(commands, 'BuildPinnedGuestImagesTarball')
+    artifacts.BundlePinnedGuestImages(self.target_request, self.response,
+                                      self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir,
+                                  'pinned-guest-images.tar.gz'))
+
   def testBundlePinnedGuestImages(self):
     """BundlePinnedGuestImages calls cbuildbot/commands with correct args."""
     build_pinned_guest_images_tarball = self.PatchObject(
@@ -345,6 +403,59 @@
     self.assertFalse(self.response.artifacts)
 
 
+class FetchPinnedGuestImagesTest(cros_test_lib.MockTempDirTestCase,
+                                 api_config.ApiConfigMixin, BundleRequestMixin):
+  """Unittests for FetchPinnedGuestImages."""
+
+  def setUp(self):
+    self.build_target = 'board'
+    self.chroot_dir = os.path.join(self.tempdir, 'chroot_dir')
+    self.sysroot_path = '/sysroot'
+    self.sysroot_dir = os.path.join(self.chroot_dir, 'sysroot')
+    osutils.SafeMakedirs(self.sysroot_dir)
+
+    self.input_request = artifacts_pb2.PinnedGuestImageUriRequest(
+        sysroot={'path': self.sysroot_path,
+                 'build_target': {'name': self.build_target}},
+        chroot={'path': self.chroot_dir})
+
+    self.response = artifacts_pb2.PinnedGuestImageUriResponse()
+
+  def testValidateOnly(self):
+    """Sanity check that a validate only call does not execute any logic."""
+    patch = self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages')
+    artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+                                     self.validate_only_config)
+    patch.assert_not_called()
+
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages')
+    artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+                                     self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.pinned_images), 1)
+    self.assertEqual(self.response.pinned_images[0].filename,
+                     'pinned_file.tar.gz')
+    self.assertEqual(self.response.pinned_images[0].uri,
+                     'https://testuri.com')
+
+  def testFetchPinnedGuestImages(self):
+    """FetchPinnedGuestImages calls service with correct args."""
+    pins = []
+    pins.append(PinnedGuestImage(
+        filename='my_pinned_file.tar.gz', uri='https://the_testuri.com'))
+    self.PatchObject(artifacts_svc, 'FetchPinnedGuestImages',
+                     return_value=pins)
+    artifacts.FetchPinnedGuestImages(self.input_request, self.response,
+                                     self.api_config)
+    self.assertEqual(len(self.response.pinned_images), 1)
+    self.assertEqual(self.response.pinned_images[0].filename,
+                     'my_pinned_file.tar.gz')
+    self.assertEqual(self.response.pinned_images[0].uri,
+                     'https://the_testuri.com')
+
+
 class BundleFirmwareTest(BundleTestCase):
   """Unittests for BundleFirmware."""
 
@@ -355,6 +466,16 @@
                              self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleTastFiles')
+    artifacts.BundleFirmware(self.sysroot_request, self.response,
+                             self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'firmware.tar.gz'))
+
   def testBundleFirmware(self):
     """BundleFirmware calls cbuildbot/commands with correct args."""
     self.PatchObject(
@@ -386,6 +507,16 @@
                                self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(commands, 'BuildEbuildLogsTarball')
+    artifacts.BundleEbuildLogs(self.target_request, self.response,
+                               self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'ebuild-logs.tar.gz'))
+
   def testBundleEbuildLogs(self):
     """BundleEbuildLogs calls cbuildbot/commands with correct args."""
     bundle_ebuild_logs_tarball = self.PatchObject(
@@ -430,6 +561,16 @@
                                    self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleChromeOSConfig')
+    artifacts.BundleChromeOSConfig(self.target_request, self.response,
+                                   self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'config.yaml'))
+
   def testBundleChromeOSConfigCallWithSysroot(self):
     """Call with a request that sets sysroot."""
     bundle_chromeos_config = self.PatchObject(
@@ -504,6 +645,16 @@
                                        self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleTestUpdatePayloads')
+    artifacts.BundleTestUpdatePayloads(self.input_proto, self.output_proto,
+                                       self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.output_proto.artifacts), 1)
+    self.assertEqual(self.output_proto.artifacts[0].path,
+                     os.path.join(self.archive_root, 'payload1.bin'))
+
   def testBundleTestUpdatePayloads(self):
     """BundleTestUpdatePayloads calls cbuildbot/commands with correct args."""
     image_path = os.path.join(self.image_root, constants.BASE_IMAGE_BIN)
@@ -581,6 +732,19 @@
                                           self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleSimpleChromeArtifacts')
+    request = self._GetRequest(chroot=self.chroot_dir,
+                               sysroot=self.sysroot_path,
+                               build_target='board', output_dir=self.output_dir)
+    artifacts.BundleSimpleChromeArtifacts(request, self.response,
+                                          self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'simple_chrome.txt'))
+
   def testNoBuildTarget(self):
     """Test no build target fails."""
     request = self._GetRequest(chroot=self.chroot_dir,
@@ -677,6 +841,18 @@
     artifacts.BundleVmFiles(in_proto, self.response, self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'BundleVmFiles')
+    in_proto = self._GetInput(chroot='/chroot/dir', sysroot='/build/board',
+                              test_results_dir='/test/results',
+                              output_dir=self.output_dir)
+    artifacts.BundleVmFiles(in_proto, self.response, self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'f1.tar'))
+
   def testChrootMissing(self):
     """Test error handling for missing chroot."""
     in_proto = self._GetInput(sysroot='/build/board',
@@ -793,6 +969,22 @@
                                             self.validate_only_config)
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc,
+                             'BundleAFDOGenerationArtifacts')
+    request = self._GetRequest(chroot=self.chroot_dir,
+                               build_target=self.build_target,
+                               chrome_root=self.chrome_root,
+                               output_dir=self.output_dir,
+                               artifact_type=self.valid_artifact_type)
+    artifacts.BundleAFDOGenerationArtifacts(request, self.response,
+                                            self.mock_call_config)
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 1)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.output_dir, 'artifact1'))
+
   def testNoBuildTarget(self):
     """Test no build target fails."""
     request = self._GetRequest(chroot=self.chroot_dir,
@@ -914,6 +1106,23 @@
 
     patch.assert_not_called()
 
+  def testMockCall(self):
+    """Test that a mock call does not execute logic, returns mocked value."""
+    patch = self.PatchObject(artifacts_svc, 'GenerateCpeReport')
+
+    request = artifacts_pb2.BundleRequest()
+    request.build_target.name = 'board'
+    request.output_dir = self.tempdir
+
+    artifacts.ExportCpeReport(request, self.response, self.mock_call_config)
+
+    patch.assert_not_called()
+    self.assertEqual(len(self.response.artifacts), 2)
+    self.assertEqual(self.response.artifacts[0].path,
+                     os.path.join(self.tempdir, 'cpe_report.txt'))
+    self.assertEqual(self.response.artifacts[1].path,
+                     os.path.join(self.tempdir, 'cpe_warnings.txt'))
+
   def testNoBuildTarget(self):
     request = artifacts_pb2.BundleRequest()
     request.output_dir = self.tempdir