def test_download_reply_timeout(mocker): api = API("mock", "mock", "mock", "mock", proxy=False) mocker.patch("sdclientapi.requests.request", side_effect=RequestTimeoutError) with pytest.raises(RequestTimeoutError): r = Reply(uuid="humanproblem", filename="secret.txt") api.download_reply(r)
def test_download_reply_timeout(mocker): class MockedPopen: def __init__(self, *nargs, **kwargs) -> None: self.stdin = mocker.MagicMock() def communicate(self, *nargs, **kwargs) -> None: raise TimeoutExpired(["mock"], 123) api = API("mock", "mock", "mock", "mock", proxy=True) mocker.patch("sdclientapi.Popen", MockedPopen) with pytest.raises(RequestTimeoutError): r = Reply(uuid="humanproblem", filename="secret.txt") api.download_reply(r)
def call_download_api(self, api: API, db_object: Reply) -> Tuple[str, str]: ''' Override DownloadJob. ''' sdk_object = SdkReply(uuid=db_object.uuid, filename=db_object.filename) sdk_object.source_uuid = db_object.source.uuid return api.download_reply(sdk_object)
def call_download_api(self, api: API, db_object: Reply) -> Tuple[str, str]: ''' Override DownloadJob. ''' sdk_object = SdkReply(uuid=db_object.uuid, filename=db_object.filename) sdk_object.source_uuid = db_object.source.uuid # TODO: Once https://github.com/freedomofpress/securedrop-sdk/issues/108 is implemented, we # will want to pass the default request timeout to download_reply instead of setting it on # the api object directly. api.default_request_timeout = 20 return api.download_reply(sdk_object)
class TestAPI(unittest.TestCase): @vcr.use_cassette("data/test-setup.yml") def setUp(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://127.0.0.1:8081/" self.api = API(self.server, self.username, self.password, str(self.totp.now())) for i in range(3): try: self.api.authenticate() except BaseError: token = load_auth() if token: self.api.token = token self.api.update_auth_header() break time.sleep(31) save_auth(self.api.token) break @vcr.use_cassette("data/test-baduser.yml") def test_auth_baduser(self): self.api = API(self.server, "no", self.password, str(self.totp.now())) with self.assertRaises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badpassword.yml") def test_auth_badpassword(self): self.api = API(self.server, self.username, "no", str(self.totp.now())) with self.assertRaises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badotp.yml") def test_auth_badotp(self): self.api = API(self.server, self.username, self.password, "no") with self.assertRaises(AuthError): self.api.authenticate() def test_api_auth(self): self.assertTrue(isinstance(self.api.token, str)) self.assertTrue( isinstance(self.api.token_expiration, datetime.datetime)) self.assertTrue(isinstance(self.api.token_journalist_uuid, str)) self.assertTrue( isinstance(self.api.journalist_first_name, (str, type(None)))) self.assertTrue( isinstance(self.api.journalist_last_name, (str, type(None)))) @vcr.use_cassette("data/test-get-sources.yml") def test_get_sources(self): sources = self.api.get_sources() for source in sources: # Assert expected fields are present assert source.journalist_designation assert source.uuid assert source.last_updated @vcr.use_cassette("data/test-star-add-remove.yml") def test_star_add_remove(self): s = self.api.get_sources()[0] self.assertTrue(self.api.add_star(s)) self.assertTrue(self.api.remove_star(s)) for source in self.api.get_sources(): if source.uuid == s.uuid: self.assertFalse(source.is_starred) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source(self): s = self.api.get_sources()[0] # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source_from_string(self): s = self.api.get_sources()[0] # Now we will try to get the same source again using uuid s2 = self.api.get_source_from_string(s.uuid) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-failed-single-source.yml") def test_failed_single_source(self): with self.assertRaises(WrongUUIDError): self.api.get_source(Source(uuid="not there")) @vcr.use_cassette("data/test-get-submissions.yml") def test_get_submissions(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) for submission in subs: assert submission.filename @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission(subs[0]) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission_from_string(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-wrong-submissions.yml") def test_get_wrong_submissions(self): s = self.api.get_sources()[0] s.uuid = "rofl-missing" with self.assertRaises(WrongUUIDError): self.api.get_submissions(s) @vcr.use_cassette("data/test-get-all-submissions.yml") def test_get_all_submissions(self): subs = self.api.get_all_submissions() for submission in subs: assert submission.filename @vcr.use_cassette("data/test-flag-source.yml") def test_flag_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.flag_source(s)) # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertTrue(s2.is_flagged) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source(s)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source_from_string(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source_from_string(s.uuid)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @vcr.use_cassette("data/test-delete-submission.yml") def test_delete_submission(self): number_of_submissions_before = len(self.api.get_all_submissions()) subs = self.api.get_all_submissions() self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-delete-submission-from-string.yml") def test_delete_submission_from_string(self): number_of_submissions_before = len(self.api.get_all_submissions()) s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-get-current-user.yml") def test_get_current_user(self): user = self.api.get_current_user() self.assertTrue(user["is_admin"]) self.assertEqual(user["username"], "journalist") self.assertTrue("first_name" in user) self.assertTrue("last_name" in user) @vcr.use_cassette("data/test-error-unencrypted-reply.yml") def test_error_unencrypted_reply(self): s = self.api.get_sources()[0] with self.assertRaises(ReplyError) as err: self.api.reply_source(s, "hello") self.assertEqual(err.exception.msg, "You must encrypt replies client side") @vcr.use_cassette("data/test-reply-source.yml") def test_reply_source(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() reply = self.api.reply_source(s, data) assert isinstance(reply, Reply) assert reply.uuid assert reply.filename @vcr.use_cassette("data/test-reply-source-with-uuid.yml") def test_reply_source_with_uuid(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855" reply = self.api.reply_source(s, data, msg_uuid) assert reply.uuid == msg_uuid @vcr.use_cassette("data/test-download-submission.yml") def test_download_submission(self): s = self.api.get_all_submissions()[0] self.assertFalse(s.is_read) # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_submission(s, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # Now the submission should have is_read as True. s = self.api.get_submission(s) self.assertTrue(s.is_read) # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-get-replies-from-source.yml") def test_get_replies_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE) @vcr.use_cassette("data/test-get-reply-from-source.yml") def test_get_reply_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) reply = replies[0] r = self.api.get_reply_from_source(s, reply.uuid) self.assertEqual(reply.filename, r.filename) self.assertEqual(reply.size, r.size) self.assertEqual(reply.reply_url, r.reply_url) self.assertEqual(reply.journalist_username, r.journalist_username) @vcr.use_cassette("data/test-get-all-replies.yml") def test_get_all_replies(self): num_sources = len(self.api.get_sources()) replies = self.api.get_all_replies() self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources) @vcr.use_cassette("data/test-download-reply.yml") def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_reply(r, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-delete-reply.yml") def test_delete_reply(self): r = self.api.get_all_replies()[0] number_of_replies_before = len(self.api.get_all_replies()) self.assertTrue(self.api.delete_reply(r)) # We deleted one, so there must be 1 less reply now self.assertEqual(len(self.api.get_all_replies()), number_of_replies_before - 1) @vcr.use_cassette("data/test-logout.yml") def test_zlogout(self): r = self.api.logout() self.assertTrue(r)
class TestAPI(TestShared): """ Note that TestShared contains most of the test code, which is shared between API and API Proxy tests. """ def setup_method(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://127.0.0.1:8081/" # Because we may be using a TOTP code from a previous run that has since # been invalidated (or that may be invalid because of bad timing), # we retry repeatedly to get the token with a new TOTP code. # # It doesn't matter if these intermittent 403s are captured in the # cassette as we ignore them during playback. auth_result = None with vcr.use_cassette("data/test-setup.yml") as cassette: for i in range(3): totp = self.totp.now() self.api = API(self.server, self.username, self.password, str(totp)) try: auth_result = self.api.authenticate() except AuthError: # Don't sleep on final retry attempt or during playback if i < 2 and cassette.play_count == 0: time.sleep(31) continue # No error, let's move on break if auth_result is None: raise AuthError("Could not obtain API token during test setup.") @vcr.use_cassette("data/test-baduser.yml") def test_auth_baduser(self): self.api = API(self.server, "no", self.password, str(self.totp.now())) with pytest.raises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badpassword.yml") def test_auth_badpassword(self): self.api = API(self.server, self.username, "no", str(self.totp.now())) with pytest.raises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badotp.yml") def test_auth_badotp(self): self.api = API(self.server, self.username, self.password, "no") with pytest.raises(AuthError): self.api.authenticate() def test_api_auth(self): super().api_auth() # This test is order-sensitive and must be run before the "seen" # state of files is altered. @vcr.use_cassette("data/test-download-submission.yml") def test_download_submission(self): submissions = self.api.get_all_submissions() unread_submission = None for s in submissions: if not s.is_read: unread_submission = s break if not unread_submission: assert False, "There must be an unread submission in the db for this test to work." assert not unread_submission.is_read # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_submission(s, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # is_read should still be False as of SecureDrop 1.6.0 or later submission = self.api.get_submission(unread_submission) assert not submission.is_read # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-seen.yml") def test_seen(self): super().seen() @vcr.use_cassette("data/test-get-sources.yml") def test_get_sources(self): super().get_sources() @vcr.use_cassette("data/test-star-add-remove.yml") def test_star_add_remove(self): super().star_add_remove() @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source(self): super().get_single_source() @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source_from_string(self): super().get_single_source(from_string=True) @vcr.use_cassette("data/test-failed-single-source.yml") def test_failed_single_source(self): super().failed_single_source() @vcr.use_cassette("data/test-get-submissions.yml") def test_get_submissions(self): super().get_submissions() @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission(self): super().get_submission() @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission_from_string(self): super().get_submission(from_string=True) @vcr.use_cassette("data/test-get-wrong-submissions.yml") def test_get_wrong_submissions(self): super().get_wrong_submissions() @vcr.use_cassette("data/test-get-all-submissions.yml") def test_get_all_submissions(self): super().get_all_submissions() @vcr.use_cassette("data/test-flag-source.yml") def test_flag_source(self): super().flag_source() @vcr.use_cassette("data/test-get-current-user.yml") def test_get_current_user(self): super().get_current_user() @vcr.use_cassette("data/test-get-users.yml") def test_get_users(self): super().get_users() @vcr.use_cassette("data/test-error-unencrypted-reply.yml") def test_error_unencrypted_reply(self): super().error_unencrypted_reply() @vcr.use_cassette("data/test-get-replies-from-source.yml") def test_get_replies_from_source(self): super().get_replies_from_source() @vcr.use_cassette("data/test-get-reply-from-source.yml") def test_get_reply_from_source(self): super().get_reply_from_source() @vcr.use_cassette("data/test-get-all-replies.yml") def test_get_all_replies(self): super().get_all_replies() # This test is materially different in the API & API Proxy versions. @vcr.use_cassette("data/test-download-reply.yml") def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_reply(r, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # Let us remove the temporary directory shutil.rmtree(tmpdir) # ORDER MATTERS: The following tests add or delete data, and should # not be run before other tests, which may rely on the original fixture # state. @vcr.use_cassette("data/test-reply-source.yml") def test_reply_source(self): super().reply_source() @vcr.use_cassette("data/test-reply-source-with-uuid.yml") def test_reply_source_with_uuid(self): super().reply_source_with_uuid() @vcr.use_cassette("data/test-delete-conversation.yml") def test_delete_conversation(self): super().delete_conversation() @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source(self): super().delete_source() @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source_from_string(self): super().delete_source(from_string=True) @vcr.use_cassette("data/test-delete-submission.yml") def test_delete_submission(self): super().delete_submission() @vcr.use_cassette("data/test-delete-submission-from-string.yml") def test_delete_submission_from_string(self): super().delete_submission(from_string=True) @vcr.use_cassette("data/test-delete-reply.yml") def test_delete_reply(self): super().delete_reply() @vcr.use_cassette("data/test-logout.yml") def test_zlogout(self): r = self.api.logout() assert r
class TestAPIProxy(unittest.TestCase): @dastollervey_datasaver def setUp(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://localhost:8081/" self.api = API(self.server, self.username, self.password, str(self.totp.now()), proxy=True) for i in range(3): try: self.api.authenticate() except BaseError: token = load_auth() if token: self.api.token = token self.api.update_auth_header() break time.sleep(31) save_auth(self.api.token) break def test_api_auth(self): self.assertTrue(self.api.token) @dastollervey_datasaver def test_get_sources(self): sources = self.api.get_sources() self.assertEqual(len(sources), 2) @dastollervey_datasaver def test_star_add_remove(self): s = self.api.get_sources()[0] self.assertTrue(self.api.add_star(s)) self.assertTrue(self.api.remove_star(s)) for source in self.api.get_sources(): if source.uuid == s.uuid: self.assertFalse(source.is_starred) @dastollervey_datasaver def test_get_single_source(self): s = self.api.get_sources()[0] # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @dastollervey_datasaver def test_get_single_source_from_string(self): s = self.api.get_sources()[0] # Now we will try to get the same source again using uuid s2 = self.api.get_source_from_string(s.uuid) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @dastollervey_datasaver def test_failed_single_source(self): with self.assertRaises(WrongUUIDError): self.api.get_source(Source(uuid="not there")) @dastollervey_datasaver def test_get_submissions(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertEqual(len(subs), 2) @dastollervey_datasaver def test_get_submission(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) sub = self.api.get_submission(subs[0]) self.assertEqual(sub.filename, subs[0].filename) @dastollervey_datasaver def test_get_submission_from_string(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid) self.assertEqual(sub.filename, subs[0].filename) @dastollervey_datasaver def test_get_wrong_submissions(self): s = self.api.get_sources()[0] s.submissions_url = "/api/v1/sources/rofl-missing/submissions/2334" s.uuid = "rofl-missing" with self.assertRaises(WrongUUIDError): self.api.get_submissions(s) @dastollervey_datasaver def test_get_all_submissions(self): subs = self.api.get_all_submissions() self.assertEqual(len(subs), 4) @dastollervey_datasaver def test_flag_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.flag_source(s)) # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertTrue(s2.is_flagged) @dastollervey_datasaver def test_delete_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source(s)) # Now there should be one source left sources = self.api.get_sources() self.assertEqual(len(sources), 1) @dastollervey_datasaver def test_delete_source_from_string(self): s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source_from_string(s.uuid)) # Now there should be one source left sources = self.api.get_sources() self.assertEqual(len(sources), 1) @dastollervey_datasaver def test_delete_submission(self): subs = self.api.get_all_submissions() self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 3 submissions self.assertEqual(len(new_subs), 3) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @dastollervey_datasaver def test_delete_submission_from_string(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 3 submissions self.assertEqual(len(new_subs), 3) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @dastollervey_datasaver def test_get_current_user(self): user = self.api.get_current_user() self.assertTrue(user["is_admin"]) self.assertEqual(user["username"], "journalist") @dastollervey_datasaver def test_error_unencrypted_reply(self): s = self.api.get_sources()[0] with self.assertRaises(ReplyError) as err: self.api.reply_source(s, "hello") self.assertEqual(err.exception.msg, "You must encrypt replies client side") @dastollervey_datasaver def test_reply_source(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() self.assertTrue(self.api.reply_source(s, data)) @dastollervey_datasaver def test_get_replies_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) self.assertEqual(len(replies), 2) @dastollervey_datasaver def test_get_reply_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) reply = replies[0] r = self.api.get_reply_from_source(s, reply.uuid) self.assertEqual(reply.filename, r.filename) self.assertEqual(reply.size, r.size) self.assertEqual(reply.reply_url, r.reply_url) self.assertEqual(reply.journalist_username, r.journalist_username) @dastollervey_datasaver def test_get_all_replies(self): replies = self.api.get_all_replies() self.assertEqual(len(replies), 4) @dastollervey_datasaver def test_delete_reply(self): r = self.api.get_all_replies()[0] self.assertTrue(self.api.delete_reply(r)) # We deleted one, so there must be 3 replies left self.assertEqual(len(self.api.get_all_replies()), 3) @dastollervey_datasaver def test_download_reply(self): r = self.api.get_all_replies()[0] _, filepath = self.api.download_reply(r) @dastollervey_datasaver def test_download_submission(self): s = self.api.get_all_submissions()[0] self.assertFalse(s.is_read) _, filepath = self.api.download_submission(s) # Now the submission should have is_read as True. s = self.api.get_submission(s) self.assertTrue(s.is_read)
class TestAPIProxy(TestShared): """ Note that TestShared contains most of the test code, which is shared between API and API Proxy tests. """ @qrexec_datasaver def setup_method(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://localhost:8081/" self.api = API(self.server, self.username, self.password, str(self.totp.now()), proxy=True) try: self.api.authenticate() except BaseError as err: raise AuthError( "Could not obtain API token during test setup. " "TOTP code may have expired or proxy may not be reachable. " "Error was: {}".format(err.msg)) @qrexec_datasaver def test_api_auth(self): super().api_auth() # This test is order-sensitive and must be run before the "seen" # state of files is altered. # This test is materially different in the API & API Proxy versions. @qrexec_datasaver def test_download_submission(self): s = self.api.get_all_submissions()[0] assert not s.is_read # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_submission(s, tmpdir) # Uncomment the following part only on QubesOS # for testing against real server. # now let us read the downloaded file # with open(filepath, "rb") as fobj: # fobj.read() # is_read should still be False as of SecureDrop 1.6.0 or later. s = self.api.get_submission(s) assert not s.is_read # Let us remove the temporary directory shutil.rmtree(tmpdir) @qrexec_datasaver def test_seen(self): super().seen() @qrexec_datasaver def test_get_sources(self): super().get_sources() @qrexec_datasaver def test_star_add_remove(self): super().star_add_remove() @qrexec_datasaver def test_get_single_source(self): super().get_single_source() @qrexec_datasaver def test_get_single_source_from_string(self): super().get_single_source(from_string=True) @qrexec_datasaver def test_failed_single_source(self): super().failed_single_source() @qrexec_datasaver def test_get_submissions(self): super().get_submissions() @qrexec_datasaver def test_get_submission(self): super().get_submission() @qrexec_datasaver def test_get_submission_from_string(self): super().get_submission(from_string=True) @qrexec_datasaver def test_get_wrong_submissions(self): super().get_wrong_submissions() @qrexec_datasaver def test_get_all_submissions(self): super().get_all_submissions() @qrexec_datasaver def test_flag_source(self): super().flag_source() @qrexec_datasaver def test_get_current_user(self): super().get_current_user() @qrexec_datasaver def test_get_users(self): super().get_users() @qrexec_datasaver def test_error_unencrypted_reply(self): super().error_unencrypted_reply() @qrexec_datasaver def test_get_replies_from_source(self): super().get_replies_from_source() @qrexec_datasaver def test_get_reply_from_source(self): super().get_reply_from_source() @qrexec_datasaver def test_get_all_replies(self): super().get_all_replies() @qrexec_datasaver def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_reply(r, tmpdir) # Uncomment the following part only on QubesOS # for testing against real server. # now let us read the downloaded file # with open(filepath, "rb") as fobj: # fobj.read() # Let us remove the temporary directory shutil.rmtree(tmpdir) # ORDER MATTERS: The following tests add or delete data, and should # not be run before other tests which may rely on the original fixture # state. @qrexec_datasaver def test_reply_source(self): super().reply_source() @qrexec_datasaver def test_reply_source_with_uuid(self): super().reply_source_with_uuid() @qrexec_datasaver def test_delete_conversation(self): super().delete_conversation() @qrexec_datasaver def test_delete_source(self): super().delete_source() @qrexec_datasaver def test_delete_source_from_string(self): super().delete_source(from_string=True) @qrexec_datasaver def test_delete_submission(self): super().delete_submission() @qrexec_datasaver def test_delete_submission_from_string(self): super().delete_submission(from_string=True) @qrexec_datasaver def test_delete_reply(self): super().delete_reply() @qrexec_datasaver def test_logout(self): r = self.api.logout() assert r
class TestAPI(unittest.TestCase): def setUp(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://127.0.0.1:8081/" # Because we may be using a TOTP code from a previous run that has since # been invalidated (or that may be invalid because of bad timing), # we retry repeatedly to get the token with a new TOTP code. # # It doesn't matter if these intermittent 403s are captured in the # cassette as we ignore them during playback. auth_result = None with vcr.use_cassette("data/test-setup.yml") as cassette: for i in range(3): totp = self.totp.now() self.api = API(self.server, self.username, self.password, str(totp)) try: auth_result = self.api.authenticate() except AuthError: # Don't sleep on final retry attempt or during playback if i < 2 and cassette.play_count == 0: time.sleep(31) continue # No error, let's move on break if auth_result is None: raise AuthError("Could not obtain API token during test setup.") @vcr.use_cassette("data/test-baduser.yml") def test_auth_baduser(self): self.api = API(self.server, "no", self.password, str(self.totp.now())) with self.assertRaises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badpassword.yml") def test_auth_badpassword(self): self.api = API(self.server, self.username, "no", str(self.totp.now())) with self.assertRaises(AuthError): self.api.authenticate() @vcr.use_cassette("data/test-badotp.yml") def test_auth_badotp(self): self.api = API(self.server, self.username, self.password, "no") with self.assertRaises(AuthError): self.api.authenticate() def test_api_auth(self): self.assertTrue(isinstance(self.api.token, str)) self.assertTrue( isinstance(self.api.token_expiration, datetime.datetime)) self.assertTrue(isinstance(self.api.token_journalist_uuid, str)) self.assertTrue(isinstance(self.api.first_name, (str, type(None)))) self.assertTrue(isinstance(self.api.last_name, (str, type(None)))) @vcr.use_cassette("data/test-seen.yml") def test_seen(self): submissions = self.api.get_all_submissions() replies = self.api.get_all_replies() file_uuids = [] message_uuids = [] reply_uuids = [] for submission in submissions: if submission.is_file(): file_uuids.append(submission.uuid) else: message_uuids.append(submission.uuid) for reply in replies: reply_uuids.append(reply.uuid) self.assertTrue( self.api.seen(files=file_uuids, messages=message_uuids, replies=reply_uuids)) @vcr.use_cassette("data/test-get-sources.yml") def test_get_sources(self): sources = self.api.get_sources() for source in sources: # Assert expected fields are present assert source.journalist_designation assert source.uuid assert source.last_updated @vcr.use_cassette("data/test-star-add-remove.yml") def test_star_add_remove(self): s = self.api.get_sources()[0] self.assertTrue(self.api.add_star(s)) self.assertTrue(self.api.remove_star(s)) for source in self.api.get_sources(): if source.uuid == s.uuid: self.assertFalse(source.is_starred) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source(self): s = self.api.get_sources()[0] # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source_from_string(self): s = self.api.get_sources()[0] # Now we will try to get the same source again using uuid s2 = self.api.get_source_from_string(s.uuid) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-failed-single-source.yml") def test_failed_single_source(self): with self.assertRaises(WrongUUIDError): self.api.get_source(Source(uuid="not there")) @vcr.use_cassette("data/test-get-submissions.yml") def test_get_submissions(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) for submission in subs: assert submission.filename @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission(subs[0]) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission_from_string(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-wrong-submissions.yml") def test_get_wrong_submissions(self): s = self.api.get_sources()[0] s.uuid = "rofl-missing" with self.assertRaises(WrongUUIDError): self.api.get_submissions(s) @vcr.use_cassette("data/test-get-all-submissions.yml") def test_get_all_submissions(self): subs = self.api.get_all_submissions() for submission in subs: assert submission.filename @vcr.use_cassette("data/test-flag-source.yml") def test_flag_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.flag_source(s)) # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertTrue(s2.is_flagged) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source(s)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source_from_string(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source_from_string(s.uuid)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @vcr.use_cassette("data/test-delete-submission.yml") def test_delete_submission(self): number_of_submissions_before = len(self.api.get_all_submissions()) subs = self.api.get_all_submissions() self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-delete-submission-from-string.yml") def test_delete_submission_from_string(self): number_of_submissions_before = len(self.api.get_all_submissions()) s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-get-current-user.yml") def test_get_current_user(self): user = self.api.get_current_user() self.assertTrue(user["is_admin"]) self.assertEqual(user["username"], "journalist") self.assertTrue("first_name" in user) self.assertTrue("last_name" in user) @vcr.use_cassette("data/test-get-users.yml") def test_get_users(self): users = self.api.get_users() for user in users: # Assert expected fields are present assert hasattr(user, "first_name") assert hasattr(user, "last_name") # Every user has a non-empty name and UUID assert user.username assert user.uuid # The API should never return these fields assert not hasattr(user, "last_login") assert not hasattr(user, "is_admin") @vcr.use_cassette("data/test-error-unencrypted-reply.yml") def test_error_unencrypted_reply(self): s = self.api.get_sources()[0] with self.assertRaises(ReplyError) as err: self.api.reply_source(s, "hello") self.assertEqual(err.exception.msg, "bad request") @vcr.use_cassette("data/test-reply-source.yml") def test_reply_source(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() reply = self.api.reply_source(s, data) assert isinstance(reply, Reply) assert reply.uuid assert reply.filename @vcr.use_cassette("data/test-reply-source-with-uuid.yml") def test_reply_source_with_uuid(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855" reply = self.api.reply_source(s, data, msg_uuid) assert reply.uuid == msg_uuid @vcr.use_cassette("data/test-download-submission.yml") def test_download_submission(self): submissions = self.api.get_all_submissions() unread_submission = None for s in submissions: if not s.is_read: unread_submission = s break if not unread_submission: self.assertFalse( "There must be an unread submission in the db for this test to work." ) self.assertFalse(unread_submission.is_read) # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_submission(s, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # is_read should still be False as of SecureDrop 1.6.0 or later submission = self.api.get_submission(unread_submission) self.assertFalse(submission.is_read) # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-get-replies-from-source.yml") def test_get_replies_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE) @vcr.use_cassette("data/test-get-reply-from-source.yml") def test_get_reply_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) reply = replies[0] r = self.api.get_reply_from_source(s, reply.uuid) self.assertEqual(reply.filename, r.filename) self.assertEqual(reply.size, r.size) self.assertEqual(reply.reply_url, r.reply_url) self.assertEqual(reply.journalist_username, r.journalist_username) @vcr.use_cassette("data/test-get-all-replies.yml") def test_get_all_replies(self): num_sources = len(self.api.get_sources()) replies = self.api.get_all_replies() self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources) @vcr.use_cassette("data/test-download-reply.yml") def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() etag, filepath = self.api.download_reply(r, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: content = fobj.read() # Verify the ETag contains the algorithm and the hash is correct hasher = hashlib.sha256() hasher.update(content) assert etag == "sha256:{}".format(hasher.hexdigest()) # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-delete-reply.yml") def test_delete_reply(self): r = self.api.get_all_replies()[0] number_of_replies_before = len(self.api.get_all_replies()) self.assertTrue(self.api.delete_reply(r)) # We deleted one, so there must be 1 less reply now self.assertEqual(len(self.api.get_all_replies()), number_of_replies_before - 1) @vcr.use_cassette("data/test-logout.yml") def test_zlogout(self): r = self.api.logout() self.assertTrue(r)
class TestAPIProxy(unittest.TestCase): @dastollervey_datasaver def setUp(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://localhost:8081/" self.api = API(self.server, self.username, self.password, str(self.totp.now()), proxy=True) for i in range(3): if os.path.exists("testtoken.json"): token = load_auth() self.api.token = token self.api.update_auth_header() break # The following is True when we did a logout if os.path.exists("logout.txt"): os.unlink("logout.txt") time.sleep(31) # Now let us try to login try: self.api = API(self.server, self.username, self.password, str(self.totp.now()), proxy=True) self.api.authenticate() with open("login.txt", "w") as fobj: fobj.write("in") except Exception as err: print(err) time.sleep(31) continue save_auth(self.api.token) break def test_api_auth(self): self.assertTrue(isinstance(self.api.token, str)) self.assertTrue( isinstance(self.api.token_expiration, datetime.datetime)) self.assertTrue(isinstance(self.api.token_journalist_uuid, str)) self.assertTrue(isinstance(self.api.first_name, (str, type(None)))) self.assertTrue(isinstance(self.api.last_name, (str, type(None)))) @dastollervey_datasaver def test_seen(self): submissions = self.api.get_all_submissions() replies = self.api.get_all_replies() file_uuids = [] message_uuids = [] reply_uuids = [] for submission in submissions: if submission.is_file(): file_uuids.append(submission.uuid) else: message_uuids.append(submission.uuid) for reply in replies: reply_uuids.append(reply.uuid) self.assertTrue( self.api.seen(files=file_uuids, messages=message_uuids, replies=reply_uuids)) @dastollervey_datasaver def test_get_sources(self): sources = self.api.get_sources() for source in sources: # Assert expected fields are present assert source.journalist_designation assert source.uuid assert source.last_updated @dastollervey_datasaver def test_star_add_remove(self): s = self.api.get_sources()[0] self.assertTrue(self.api.add_star(s)) self.assertTrue(self.api.remove_star(s)) for source in self.api.get_sources(): if source.uuid == s.uuid: self.assertFalse(source.is_starred) @dastollervey_datasaver def test_get_single_source(self): s = self.api.get_sources()[0] # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @dastollervey_datasaver def test_get_single_source_from_string(self): s = self.api.get_sources()[0] # Now we will try to get the same source again using uuid s2 = self.api.get_source_from_string(s.uuid) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @dastollervey_datasaver def test_failed_single_source(self): with self.assertRaises(WrongUUIDError): self.api.get_source(Source(uuid="not there")) @dastollervey_datasaver def test_get_submissions(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) for submission in subs: assert submission.filename @dastollervey_datasaver def test_get_submission(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission(subs[0]) self.assertEqual(sub.filename, subs[0].filename) @dastollervey_datasaver def test_get_submission_from_string(self): # Get a source with submissions source_uuid = self.api.get_all_submissions()[0].source_uuid s = self.api.get_source(Source(uuid=source_uuid)) subs = self.api.get_submissions(s) sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid) self.assertEqual(sub.filename, subs[0].filename) @dastollervey_datasaver def test_get_wrong_submissions(self): s = self.api.get_sources()[0] s.uuid = "rofl-missing" with self.assertRaises(WrongUUIDError): self.api.get_submissions(s) @dastollervey_datasaver def test_get_all_submissions(self): subs = self.api.get_all_submissions() for submission in subs: assert submission.filename @dastollervey_datasaver def test_flag_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.flag_source(s)) # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertTrue(s2.is_flagged) @dastollervey_datasaver def test_delete_source(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source(s)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @dastollervey_datasaver def test_delete_source_from_string(self): number_of_sources_before = len(self.api.get_sources()) s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source_from_string(s.uuid)) # Now there should be one less source sources = self.api.get_sources() self.assertEqual(len(sources), number_of_sources_before - 1) @dastollervey_datasaver def test_delete_submission(self): number_of_submissions_before = len(self.api.get_all_submissions()) subs = self.api.get_all_submissions() self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @dastollervey_datasaver def test_delete_submission_from_string(self): number_of_submissions_before = len(self.api.get_all_submissions()) s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 1 less submission self.assertEqual(len(new_subs), number_of_submissions_before - 1) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @dastollervey_datasaver def test_get_current_user(self): user = self.api.get_current_user() self.assertTrue(user["is_admin"]) self.assertEqual(user["username"], "journalist") self.assertTrue("first_name" in user) self.assertTrue("last_name" in user) @dastollervey_datasaver def test_get_users(self): users = self.api.get_users() for user in users: # Assert expected fields are present assert hasattr(user, "first_name") assert hasattr(user, "last_name") # Every user has a non-empty name and UUID assert user.username assert user.uuid # The API should never return these fields assert not hasattr(user, "last_login") assert not hasattr(user, "is_admin") @dastollervey_datasaver def test_error_unencrypted_reply(self): s = self.api.get_sources()[0] with self.assertRaises(ReplyError) as err: self.api.reply_source(s, "hello") self.assertEqual(err.exception.msg, "bad request") @dastollervey_datasaver def test_reply_source(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() reply = self.api.reply_source(s, data) assert isinstance(reply, Reply) assert reply.uuid assert reply.filename @dastollervey_datasaver def test_reply_source_with_uuid(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855" reply = self.api.reply_source(s, data, msg_uuid) assert reply.uuid == msg_uuid @dastollervey_datasaver def test_download_submission(self): s = self.api.get_all_submissions()[0] self.assertFalse(s.is_read) # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_submission(s, tmpdir) # Uncomment the following part only on QubesOS # for testing against real server. # now let us read the downloaded file # with open(filepath, "rb") as fobj: # fobj.read() # is_read should still be False as of SecureDrop 1.6.0 or later. s = self.api.get_submission(s) self.assertFalse(s.is_read) # Let us remove the temporary directory shutil.rmtree(tmpdir) @dastollervey_datasaver def test_get_replies_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE) @dastollervey_datasaver def test_get_reply_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) reply = replies[0] r = self.api.get_reply_from_source(s, reply.uuid) self.assertEqual(reply.filename, r.filename) self.assertEqual(reply.size, r.size) self.assertEqual(reply.reply_url, r.reply_url) self.assertEqual(reply.journalist_username, r.journalist_username) @dastollervey_datasaver def test_get_all_replies(self): num_sources = len(self.api.get_sources()) replies = self.api.get_all_replies() self.assertEqual(len(replies), NUM_REPLIES_PER_SOURCE * num_sources) @dastollervey_datasaver def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_reply(r, tmpdir) # Uncomment the following part only on QubesOS # for testing against real server. # now let us read the downloaded file # with open(filepath, "rb") as fobj: # fobj.read() # Let us remove the temporary directory shutil.rmtree(tmpdir) @dastollervey_datasaver def test_delete_reply(self): r = self.api.get_all_replies()[0] number_of_replies_before = len(self.api.get_all_replies()) self.assertTrue(self.api.delete_reply(r)) # We deleted one, so there must be 1 less reply now self.assertEqual(len(self.api.get_all_replies()), number_of_replies_before - 1) @dastollervey_datasaver def test_logout(self): r = self.api.logout() self.assertTrue(r) if os.path.exists("login.txt"): os.unlink("login.txt") if os.path.exists("testtoken.json"): os.unlink("testtoken.json") with open("logout.txt", "w") as fobj: fobj.write("out")
class TestAPI(unittest.TestCase): @vcr.use_cassette("data/test-setup.yml") def setUp(self): self.totp = pyotp.TOTP("JHCOGO7VCER3EJ4L") self.username = "******" self.password = "******" self.server = "http://*****:*****@vcr.use_cassette("data/test-get-sources.yml") def test_get_sources(self): sources = self.api.get_sources() self.assertEqual(len(sources), 2) @vcr.use_cassette("data/test-star-add-remove.yml") def test_star_add_remove(self): s = self.api.get_sources()[0] self.assertTrue(self.api.add_star(s)) self.assertTrue(self.api.remove_star(s)) for source in self.api.get_sources(): if source.uuid == s.uuid: self.assertFalse(source.is_starred) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source(self): s = self.api.get_sources()[0] # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-get-single-source.yml") def test_get_single_source_from_string(self): s = self.api.get_sources()[0] # Now we will try to get the same source again using uuid s2 = self.api.get_source_from_string(s.uuid) self.assertEqual(s.journalist_designation, s2.journalist_designation) self.assertEqual(s.uuid, s2.uuid) @vcr.use_cassette("data/test-failed-single-source.yml") def test_failed_single_source(self): with self.assertRaises(WrongUUIDError): self.api.get_source(Source(uuid="not there")) @vcr.use_cassette("data/test-get-submissions.yml") def test_get_submissions(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertEqual(len(subs), 2) @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) sub = self.api.get_submission(subs[0]) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-submission.yml") def test_get_submission_from_string(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) sub = self.api.get_submission_from_string(subs[0].uuid, s.uuid) self.assertEqual(sub.filename, subs[0].filename) @vcr.use_cassette("data/test-get-wrong-submissions.yml") def test_get_wrong_submissions(self): s = self.api.get_sources()[0] s.uuid = "rofl-missing" with self.assertRaises(WrongUUIDError): self.api.get_submissions(s) @vcr.use_cassette("data/test-get-all-submissions.yml") def test_get_all_submissions(self): subs = self.api.get_all_submissions() self.assertEqual(len(subs), 4) @vcr.use_cassette("data/test-flag-source.yml") def test_flag_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.flag_source(s)) # Now we will try to get the same source again s2 = self.api.get_source(s) self.assertTrue(s2.is_flagged) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source(self): s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source(s)) # Now there should be one source left sources = self.api.get_sources() self.assertEqual(len(sources), 1) @vcr.use_cassette("data/test-delete-source.yml") def test_delete_source_from_string(self): s = self.api.get_sources()[0] self.assertTrue(self.api.delete_source_from_string(s.uuid)) # Now there should be one source left sources = self.api.get_sources() self.assertEqual(len(sources), 1) @vcr.use_cassette("data/test-delete-submission.yml") def test_delete_submission(self): subs = self.api.get_all_submissions() self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 3 submissions self.assertEqual(len(new_subs), 3) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-delete-submission-from-string.yml") def test_delete_submission_from_string(self): s = self.api.get_sources()[0] subs = self.api.get_submissions(s) self.assertTrue(self.api.delete_submission(subs[0])) new_subs = self.api.get_all_submissions() # We now should have 3 submissions self.assertEqual(len(new_subs), 3) # Let us make sure that sub[0] is not there for s in new_subs: self.assertNotEqual(s.uuid, subs[0].uuid) @vcr.use_cassette("data/test-get-current-user.yml") def test_get_current_user(self): user = self.api.get_current_user() self.assertTrue(user["is_admin"]) self.assertEqual(user["username"], "journalist") @vcr.use_cassette("data/test-error-unencrypted-reply.yml") def test_error_unencrypted_reply(self): s = self.api.get_sources()[0] with self.assertRaises(ReplyError) as err: self.api.reply_source(s, "hello") self.assertEqual(err.exception.msg, "You must encrypt replies client side") @vcr.use_cassette("data/test-reply-source.yml") def test_reply_source(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() reply = self.api.reply_source(s, data) assert isinstance(reply, Reply) assert reply.uuid assert reply.filename @vcr.use_cassette("data/test-reply-source-with-uuid.yml") def test_reply_source_with_uuid(self): s = self.api.get_sources()[0] dirname = os.path.dirname(__file__) with open(os.path.join(dirname, "encrypted_msg.asc")) as fobj: data = fobj.read() msg_uuid = "e467868c-1fbb-4b5e-bca2-87944ea83855" reply = self.api.reply_source(s, data, msg_uuid) assert reply.uuid == msg_uuid @vcr.use_cassette("data/test-download-submission.yml") def test_download_submission(self): s = self.api.get_all_submissions()[0] self.assertFalse(s.is_read) # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_submission(s, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: fobj.read() # Now the submission should have is_read as True. s = self.api.get_submission(s) self.assertTrue(s.is_read) # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-get-replies-from-source.yml") def test_get_replies_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) self.assertEqual(len(replies), 2) @vcr.use_cassette("data/test-get-reply-from-source.yml") def test_get_reply_from_source(self): s = self.api.get_sources()[0] replies = self.api.get_replies_from_source(s) reply = replies[0] r = self.api.get_reply_from_source(s, reply.uuid) self.assertEqual(reply.filename, r.filename) self.assertEqual(reply.size, r.size) self.assertEqual(reply.reply_url, r.reply_url) self.assertEqual(reply.journalist_username, r.journalist_username) @vcr.use_cassette("data/test-get-all-replies.yml") def test_get_all_replies(self): replies = self.api.get_all_replies() self.assertEqual(len(replies), 4) @vcr.use_cassette("data/test-download-reply.yml") def test_download_reply(self): r = self.api.get_all_replies()[0] # We need a temporary directory to download tmpdir = tempfile.mkdtemp() _, filepath = self.api.download_reply(r, tmpdir) # now let us read the downloaded file with open(filepath, "rb") as fobj: fobj.read() # Let us remove the temporary directory shutil.rmtree(tmpdir) @vcr.use_cassette("data/test-delete-reply.yml") def test_delete_reply(self): r = self.api.get_all_replies()[0] self.assertTrue(self.api.delete_reply(r)) # We deleted one, so there must be 3 replies left self.assertEqual(len(self.api.get_all_replies()), 3)