import builtins import io import os import pytest import unittest import sys from contextlib import contextmanager, redirect_stdout import requests from kinto_http import Client, exceptions from kinto_wizard.__main__ import main def load(server, auth, file, bucket=None, collection=None, extra=None): cmd = 'kinto-wizard {} --server={} --auth={}' if bucket: cmd += ' --bucket={}'.format(bucket) if collection: cmd += ' --collection={}'.format(collection) if extra: cmd += ' ' + extra load_cmd = cmd.format("load {}".format(file), server, auth) sys.argv = load_cmd.strip().split(" ") return main() def dump(server, auth, bucket=None, collection=None): cmd = 'kinto-wizard {} --server={} --auth={}' dump_cmd = cmd.format("dump --full", server, auth) if bucket: dump_cmd += ' --bucket={}'.format(bucket) if collection: dump_cmd += ' --collection={}'.format(collection) sys.argv = dump_cmd.split(" ") output = io.StringIO() with redirect_stdout(output): main() output.flush() # Check that identical to original file. return output.getvalue() def validate(filename): sys.argv = ['kinto-wizard', 'validate', filename] return main() class FunctionalTest(unittest.TestCase): server = os.getenv("SERVER_URL", "http://localhost:8888/v1") auth = os.getenv("AUTH", "user:pass") file = os.getenv("FILE", "tests/kinto.yaml") def setUp(self): requests.post(self.server + "/__flush__") def load(self, bucket=None, collection=None, filename=None, extra=None): return load(self.server, self.auth, filename or self.file, bucket, collection, extra) def dump(self, bucket=None, collection=None): return dump(self.server, self.auth, bucket, collection) def validate(self, filename=None, code=0): try: validate(filename or self.file) except SystemExit as e: if e.code == code: return else: self.fail(f"Unexpected validation status {e.code} != {code}") class DryRunLoad(FunctionalTest): def test_dry_round_trip(self): cmd = 'kinto-wizard {} --server={} --auth={} --dry-run' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) with pytest.raises(exceptions.KintoException): client.get_bucket(id="staging") @contextmanager def mockInput(mock): original_input = builtins.input builtins.input = lambda _: mock yield builtins.input = original_input class SimpleDump(FunctionalTest): def test_round_trip(self): cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() dump_cmd = cmd.format("dump", self.server, self.auth) sys.argv = dump_cmd.split(" ") output = io.StringIO() with redirect_stdout(output): main() output.flush() # Check that identical to original file. generated = output.getvalue() with open(self.file) as f: assert f.read() == generated class FullDump(FunctionalTest): file = os.getenv("FILE", "tests/kinto-full.yaml") def test_round_trip(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() cmd = 'kinto-wizard {} --server={} --auth={} --full' load_cmd = cmd.format("dump", self.server, self.auth) sys.argv = load_cmd.split(" ") output = io.StringIO() with redirect_stdout(output): main() output.flush() # Check that identical to original file. generated = output.getvalue() with open(self.file) as f: assert f.read() == generated def test_round_trip_with_client_wins(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() # Change something that could make the server to fail. client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) client.update_record(bucket='build-hub', collection='archives', id='0831d549-0a69-48dd-b240-feef94688d47', data={}) record = client.get_record(bucket='build-hub', collection='archives', id='0831d549-0a69-48dd-b240-feef94688d47') assert set(record['data'].keys()) == {'id', 'last_modified'} cmd = 'kinto-wizard {} --server={} -D --auth={} --force' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() record = client.get_record(bucket='build-hub', collection='archives', id='0831d549-0a69-48dd-b240-feef94688d47') assert set(record['data'].keys()) != {'id', 'last_modified'} def test_round_trip_with_client_wins_and_delete_missing_records(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() # Change something that could make the server to fail. client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) client.create_record(bucket='build-hub', collection='archives', id='8031d549-0a69-48dd-b240-feef94688d47', data={}) cmd = 'kinto-wizard {} --server={} -D --auth={} --force --delete-records' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() with pytest.raises(exceptions.KintoException) as exc: client.get_record(bucket='build-hub', collection='archives', id='8031d549-0a69-48dd-b240-feef94688d47') assert "'Not Found'" in str(exc.value) def test_round_trip_with_delete_missing_records_ask_for_confirmation(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() # Change something that could make the server to fail. client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) client.create_record(bucket='build-hub', collection='archives', id='8031d549-0a69-48dd-b240-feef94688d47', data={}) cmd = 'kinto-wizard {} --server={} -D --auth={} --delete-records' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") with mockInput('yes'): main() with pytest.raises(exceptions.KintoException) as exc: client.get_record(bucket='build-hub', collection='archives', id='8031d549-0a69-48dd-b240-feef94688d47') assert "'Not Found'" in str(exc.value) def test_round_trip_with_delete_missing_records_handle_misconfirmation(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() # Change something that could make the server to fail. client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) client.create_record(bucket='build-hub', collection='archives', id='8031d549-0a69-48dd-b240-feef94688d47', data={}) cmd = 'kinto-wizard {} --server={} -D --auth={} --delete-records' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") with mockInput('no'): with pytest.raises(SystemExit): main() class DataRecordsDump(FunctionalTest): file = os.getenv("FILE", "tests/kinto-full.yaml") def test_round_trip(self): # Load some data cmd = 'kinto-wizard {} --server={} --auth={}' load_cmd = cmd.format("load {}".format(self.file), self.server, self.auth) sys.argv = load_cmd.split(" ") main() cmd = 'kinto-wizard {} --server={} --auth={} --data --records' load_cmd = cmd.format("dump", self.server, self.auth) sys.argv = load_cmd.split(" ") output = io.StringIO() with redirect_stdout(output): main() output.flush() # Check that identical to original file. generated = output.getvalue() with open(self.file) as f: assert f.read() == generated class BucketCollectionSelectionableDump(FunctionalTest): file = os.getenv("FILE", "tests/dumps/dump-full.yaml") def test_validate(self): self.validate() def test_round_trip_with_bucket_selection_on_load(self): self.load(bucket="natim") generated = self.dump() with open("tests/dumps/dump-natim.yaml") as f: assert f.read() == generated def test_round_trip_with_bucket_selection(self): self.load() generated = self.dump(bucket="natim") with open("tests/dumps/dump-natim.yaml") as f: assert f.read() == generated def test_round_trip_with_bucket_collection_selection_on_load(self): self.load(bucket="natim", collection="toto") generated = self.dump() with open("tests/dumps/dump-natim-toto-groups.yaml") as f: assert f.read() == generated def test_round_trip_with_bucket_collection_selection(self): self.load() generated = self.dump(bucket="natim", collection="toto") with open("tests/dumps/dump-natim-toto.yaml") as f: assert f.read() == generated def test_round_trip_with_collection_selection_on_load(self): self.load(collection="toto") generated = self.dump() with open("tests/dumps/dump-toto-groups.yaml") as f: assert f.read() == generated def test_round_trip_with_collection_selection(self): self.load() generated = self.dump(collection="toto") with open("tests/dumps/dump-toto.yaml") as f: assert f.read() == generated def test_wizard_can_handle_dates(self): self.load(bucket="date") generated = self.dump() with open("tests/dumps/dump-date.yaml") as f: assert f.read() == generated class YAMLReferenceSupportTest(FunctionalTest): file = os.getenv("FILE", "tests/dumps/with-references.yaml") def test_validate(self): self.validate() def test_file_can_have_yaml_references(self): self.load() client = Client(server_url=self.server, auth=tuple(self.auth.split(':'))) collection = client.get_collection(bucket="main", id="certificates") assert 'url' in collection['data']['schema']['properties'] collection = client.get_collection(bucket="main", id="addons") assert 'url' in collection['data']['schema']['properties'] # the anchor did not get interpreted as a bucket: with self.assertRaises(exceptions.KintoException): client.get_collection(bucket="attachment-schema") class WrongSchemaValidationTest(FunctionalTest): file = "tests/dumps/wrong-schema.yaml" def test_validate(self): self.validate(code=1) class MiscUpdates(FunctionalTest): def get_client(self): return Client(server_url=self.server, auth=tuple(self.auth.split(':'))) def test_validate(self): # This dump has a schema that requires `title` field, and a record doesn't have it. self.validate(filename="tests/dumps/with-schema-1.yaml", code=1) # This dump has a schema that does not require `title` field, so the dump is valid. self.validate(filename="tests/dumps/with-schema-2.yaml") def test_raises_with_4xx_error_in_batch(self): with pytest.raises(exceptions.KintoBatchException): self.load(filename="tests/dumps/with-schema-1.yaml") records = self.get_client().get_records(bucket="natim", collection="toto") assert len(records) == 0 def test_ignore_batch_4xx_errors_if_specified(self): # Raises a KintoBatchException in case of error self.load(filename="tests/dumps/with-schema-1.yaml", extra="--ignore-batch-4xx") def test_record_updates(self): self.load(filename="tests/dumps/with-schema-1.yaml", extra="--ignore-batch-4xx") client = self.get_client() client.create_record(data={'title': 'titi', 'last_modified': 1496132479110}, id="e2686bac-c45e-4144-9738-edfeb3d9da6d", collection='toto', bucket='natim') self.load(filename="tests/dumps/with-schema-2.yaml") r = client.get_record(id="e2686bac-c45e-4144-9738-edfeb3d9da6d", collection='toto', bucket='natim') assert r["data"]["title"] == "toto" def test_group_updates(self): self.load(filename="tests/dumps/with-groups.yaml") client = self.get_client() client.update_group(data={"members": ["alexis", "mathieu", "remy"]}, id="toto", bucket="natim") self.load(filename="tests/dumps/with-groups.yaml") r = client.get_group(id="toto", bucket='natim') assert r["data"]["members"] == ["alexis", "mathieu"]