def test_delete_request_removes_data(conf, env, fxa_account, fxa_urls, fxa_client): if env == 'prod': pytest.skip('kintowe GDPR tests are not run in production') auth = FxABearerTokenAuth( fxa_account.email, fxa_account.password, scopes=['sync:addon_storage'], client_id=DEFAULT_CLIENT_ID, account_server_url=fxa_urls['authentication'], oauth_server_url=fxa_urls['oauth'], ) # Add some data to chrome.storage (kintowe) we_client = Client(server_url=conf.get(env, 'we_server_url'), auth=auth) we_existing_records = we_client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(we_existing_records) == 0 data = {"payload": {"encrypted": "SmluZ28gdGVzdA=="}} we_record = we_client.create_record( data=data, collection=conf.get(env, 'qa_collection'), bucket='default', permissions={"read": ["system.Everyone"]}) we_record_id = we_record['data']['id'] we_updated_records = we_client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(we_updated_records) == len(we_existing_records) + 1 # Get the aliases of the bucket we are putting data in and # make sure that an unauthenticated user can see these records # before we delete the account we_bucket_id = we_client.server_info()["user"]["bucket"] anon_we_client = Client(server_url=conf.get(env, 'we_server_url')) resp = anon_we_client.get_record(id=we_record_id, bucket=we_bucket_id, collection=conf.get(env, 'qa_collection')) assert resp['data']['id'] == we_record_id # Delete FxA account fxa_client.destroy_account(fxa_account.email, fxa_account.password) # Wait 1 minute and then make sure the records do not exist because the # Kinto client will throw an exception for non-existent records time.sleep(60) with pytest.raises(KintoException): resp = anon_we_client.get_record(id=we_record_id, bucket=we_bucket_id, collection=conf.get( env, 'qa_collection'))
def test_delete_request_removes_data(conf, env, fxa_account, fxa_urls, fxa_client): if env == 'prod': pytest.skip('testpilot GDPR tests are not run in production') auth = FxABearerTokenAuth( fxa_account.email, fxa_account.password, client_id=DEFAULT_CLIENT_ID, scopes=["https://identity.mozilla.com/apps/notes"], account_server_url=fxa_urls['authentication'], oauth_server_url=fxa_urls['oauth'], ) # Add some data to the Notes collection tp_client = Client(server_url=conf.get(env, 'tp_server_url'), auth=auth) tp_existing_records = tp_client.get_records(collection='notes', bucket='default') assert len(tp_existing_records) == 0 data = {"subject": "QA Test", "value": "This stuff should get deleted"} tp_record = tp_client.create_record( data=data, collection='notes', permissions={"read": ["system.Everyone"]}) tp_record_id = tp_record['data']['id'] tp_updated_records = tp_client.get_records(collection='notes', bucket='default') assert len(tp_updated_records) == len(tp_existing_records) + 1 # Get the aliases of the bucket we are putting data in and # make sure that an unauthenticated user can see these records # before we delete the account tp_bucket_id = tp_client.server_info()["user"]["bucket"] anon_tp_client = Client(server_url=conf.get(env, 'tp_server_url')) resp = anon_tp_client.get_record(id=tp_record_id, bucket=tp_bucket_id, collection='notes') assert resp['data']['id'] == tp_record_id # Delete FxA account fxa_client.destroy_account(fxa_account.email, fxa_account.password) # Wait 5 minutes and then make sure the records do not exist because the # Kinto client will throw an exception for non-existent records time.sleep(120) with pytest.raises(KintoException): resp = anon_tp_client.get_record(id=tp_record_id, bucket=tp_bucket_id, collection='notes')
def test_add_content(env, conf): # Grab a bearer token that we can use to talk to the webextensions endpoint acct = TestEmailAccount() email = acct.email passwd = str(uuid.uuid4()) fxaclient = FxaClient("https://api.accounts.firefox.com") session = fxaclient.create_account(email, passwd) m = acct.wait_for_email(lambda m: "x-verify-code" in m["headers"]) if m is None: raise RuntimeErrors("Verification email did not arrive") session.verify_email_code(m["headers"]["x-verify-code"]) auth = FxABearerTokenAuth( email, passwd, scopes=['sync:addon_storage'], client_id=DEFAULT_CLIENT_ID, account_server_url=conf.get(env, 'account_server_url'), oauth_server_url=conf.get(env, 'oauth_server_url'), ) client = Client(server_url=conf.get(env, 'we_server_url'), auth=auth) # Add a record to our QA collection and make sure we have N+1 records existing_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(existing_records) == 0 data = {"payload": {"encrypted": "SmluZ28gdGVzdA=="}} resp = client.create_record(data=data, collection=conf.get(env, 'qa_collection'), bucket='default') new_record_id = resp['data']['id'] updated_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(updated_records) == len(existing_records) + 1 client.delete_record(id=new_record_id, collection=conf.get(env, 'qa_collection')) updated_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(updated_records) == len(existing_records) # Clean up the account that we created for the test acct.clear() fxaclient.destroy_account(email, passwd)
def validate_changes_collection(event, context, **kwargs): """Validate the entries of the monitor endpoint. """ # 1. Grab the changes collection server_url = event["server"] bucket = event.get("bucket", os.getenv("BUCKET", "monitor")) collection = event.get("collection", os.getenv("COLLECTION", "changes")) client = Client(server_url=server_url, bucket=bucket, collection=collection) print("Looking at %s: " % client.get_endpoint("collection")) collections = client.get_records() # 2. For each collection there, validate the ETag everything_ok = True for collection in collections: bid = collection["bucket"] cid = collection["collection"] last_modified = collection["last_modified"] etag = client.get_records_timestamp(bucket=bid, collection=cid) if str(etag) == str(last_modified): print("Etag OK for {}/{} : {}".format(bid, cid, etag)) else: everything_ok = False print("Etag NOT OK for {}/{} : {} != {}".format( bid, cid, last_modified, etag)) if not everything_ok: raise ValueError("One of the collection did not validate.")
def _has_inconsistencies(server_url, auth, r): client = Client(server_url=server_url, auth=auth) source_metadata = client.get_collection( bucket=r["source"]["bucket"], id=r["source"]["collection"])["data"] status = source_metadata["status"] identifier = "{bucket}/{collection}".format(**r["destination"]) # Collection status is reset on any modification, so if status is ``to-review``, # then records in the source should be exactly the same as the records in the preview if status == "to-review": source_records = client.get_records(**r["source"]) preview_records = client.get_records(**r["preview"]) diff = compare_collections(source_records, preview_records) if diff: return identifier, diff # And if status is ``signed``, then records in the source and preview should # all be the same as those in the destination. elif status == "signed" or status is None: source_records = client.get_records(**r["source"]) dest_records = client.get_records(**r["destination"]) if "preview" in r: # If preview is enabled, then compare source/preview and preview/dest preview_records = client.get_records(**r["preview"]) diff_source = compare_collections(source_records, preview_records) diff_preview = compare_collections(preview_records, dest_records) else: # Otherwise, just compare source/dest diff_source = compare_collections(source_records, dest_records) diff_preview = [] # If difference detected, report it! if diff_source or diff_preview: return identifier, diff_source + diff_preview else: # And if status is ``work-in-progress``, we can't really check anything. # Source can differ from preview, and preview can differ from destination # if a review request was previously rejected. print(f"{identifier} SKIP ({status})") return identifier, None print(f"{identifier} OK") return identifier, None
def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record({'foo': 'bar'}) client.delete_records() assert len(client.get_records()) == 0
def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}) client.delete_records() assert len(client.get_records()) == 0
def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) records = client.get_records() assert len(records) == 1
def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) records = client.get_records() assert len(records) == 1
def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record(data={'foo': 'bar'}) deleted = client.delete_record(id=record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0
def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0
def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() client.create_record(data={"foo": "bar"}) client.delete_records() assert len(client.get_records()) == 0
def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10
def fetch_signed_resources(server_url, auth): # List signed collection using capabilities. client = Client(server_url=server_url, auth=auth, bucket="monitor", collection="changes") info = client.server_info() try: resources = info["capabilities"]["signer"]["resources"] except KeyError: raise ValueError( "No signer capabilities found. Run on *writer* server!") # Build the list of signed collections, source -> preview -> destination # For most cases, configuration of signed resources is specified by bucket and # does not contain any collection information. resources_by_bid = {} resources_by_cid = {} preview_buckets = set() for resource in resources: if resource["source"]["collection"] is not None: resources_by_cid[( resource["destination"]["bucket"], resource["destination"]["collection"], )] = resource else: resources_by_bid[resource["destination"]["bucket"]] = resource if "preview" in resource: preview_buckets.add(resource["preview"]["bucket"]) print("Read collection list from {}".format( client.get_endpoint("collection"))) resources = [] monitored = client.get_records(_sort="bucket,collection") for entry in monitored: bid = entry["bucket"] cid = entry["collection"] # Skip preview collections entries if bid in preview_buckets: continue if (bid, cid) in resources_by_cid: r = resources_by_cid[(bid, cid)] elif bid in resources_by_bid: r = copy.deepcopy(resources_by_bid[bid]) r["source"]["collection"] = r["destination"]["collection"] = cid if "preview" in r: r["preview"]["collection"] = cid else: raise ValueError(f"Unknown signed collection {bid}/{cid}") resources.append(r) return resources
def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() record = client.create_record(data={"foo": "bar"}) deleted = client.delete_record(id=record["data"]["id"]) assert deleted["deleted"] is True assert len(client.get_records()) == 0
def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) records = client.get_records() assert len(records) == 1
def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10
def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10
def download_collection_data(server_url, collection): client = Client( server_url=server_url, bucket=collection["bucket"], collection=collection["collection"], ) endpoint = client.get_endpoint("collection") # Collection metadata with cache busting metadata = client.get_collection( _expected=collection["last_modified"])["data"] # Download records with cache busting records = client.get_records(_sort="-last_modified", _expected=collection["last_modified"]) timestamp = client.get_records_timestamp() return (collection, endpoint, metadata, records, timestamp)
def test_add_content(env, conf, fxa_account, fxa_urls): if env == 'prod': pytest.skip('qa cannot create records in production') auth = FxABearerTokenAuth( fxa_account.email, fxa_account.password, scopes=['sync:addon_storage'], client_id=DEFAULT_CLIENT_ID, account_server_url=fxa_urls['authentication'], oauth_server_url=fxa_urls['oauth'], ) client = Client(server_url=conf.get(env, 'we_server_url'), auth=auth) # Add a record to our QA collection and make sure we have N+1 records existing_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(existing_records) == 0 data = {"payload": {"encrypted": "SmluZ28gdGVzdA=="}} resp = client.create_record(data=data, collection=conf.get(env, 'qa_collection'), bucket='default') new_record_id = resp['data']['id'] updated_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(updated_records) == len(existing_records) + 1 client.delete_record(id=new_record_id, collection=conf.get(env, 'qa_collection')) updated_records = client.get_records(collection=conf.get( env, 'qa_collection'), bucket='default') assert len(updated_records) == len(existing_records)
def get_nightly_builds(from_datestr="2018-10", to_datestr="2018-11"): """Return nightly build data for all builds.""" client = KintoClient(server_url=BUILDHUB_URL) records = client.get_records( **{ "target.platform": "linux-x86_64", "target.channel": "nightly", "source.product": "firefox", "target.locale": "en-US", # Caution: use build.date because download.date is crazy for dates before # 2016. "gt_build.date": from_datestr, "lt_build.date": to_datestr, "_sort": "-download.date", "_fields": "build.id,source.revision,download.date", # "_limit": 10, }, bucket="build-hub", collection="releases", ) return records
def test_blocklist_timestamp(env, conf): if env == 'prod': pytest.skip('Skipping blocklist timestamp test in production') client = Client(server_url=conf.get(env, 'reader_server'), bucket='blocklists') # Take the highest timestamp of the collections contained in the blocklist.xml. last_modified = -1 for cid in ('addons', 'plugins', 'gfx'): records = client.get_records(collection=cid, _sort='-last_modified', _limit=1, enabled='true') if len(records) > 0: last_modified = max(last_modified, records[0]['last_modified']) # Read the current XML blocklist ETag. blocklist_uri = conf.get(env, 'reader_server').strip('/') + ( '/blocklist/3/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/58.0' '/Firefox/20180123185941/Darwin_x86_64-gcc3-u-i386-x86_64' '/en-US/release/Darwin 17.4.0/default/default/invalid/invalid/0/') r = requests.get(blocklist_uri) r.raise_for_status() etag_header = int(r.headers.get('ETag', '')[1:-1]) last_modified_header = r.headers.get('Last-Modified', '') last_modified_header = datetime.datetime.strptime( last_modified_header, '%a, %d %b %Y %H:%M:%S GMT') # Check XML attribute <blocklist lastupdate="1483471392954"> dom = minidom.parseString(r.text) root = dom.getElementsByTagName('blocklist')[0] last_modified_attr = int(root.getAttribute('lastupdate')) # Make sure they all match. # See https://bugzilla.mozilla.org/show_bug.cgi?id=1436469 assert last_modified == etag_header last_modified_dt = datetime.datetime.utcfromtimestamp(last_modified / 1000.0) assert last_modified_dt.replace(microsecond=0) == last_modified_header assert last_modified == last_modified_attr
def getmap(collection_id): processed_projects_list = [] for file in os.listdir("projects"): if file.endswith(".json"): file = os.path.join("projects", file) with open(file) as json_data: d = json.load(json_data) processed_projects_list.append(d['id']) auth = (username, password) client = Client(server_url=server_url, auth=auth) try: records = client.get_records(bucket='formdata', collection=collection_id) except: return 'There was a problem getting the information from kinto' for record in records: if record['id'] in processed_projects_list: continue else: label = record['label'] print(f'Processing JSON file for: {label}') record['element type'] = "Project" tagline = record['tag line'] description1 = record['Description1'] description2 = record['Description2'] video = record['video src'] markdown = f'#{tagline}\n##About {label}\n![About {label}]({video})\n ###{description1}\n{description2}' record['description'] = markdown stack_list = [] if 'Stack' in record: if isinstance(record['Stack'], (list, )): for value in record['Stack']: stack_list.append(remove_parens(value)) else: stack_list.append(remove_parens(record['Stack'])) record['Stack'] = stack_list network_list = [] if 'Network Topology' in record: if isinstance(record['Network Topology'], (list, )): for value in record['Network Topology']: network_list.append(remove_parens(value)) else: network_list.append( remove_parens(record['Network Topology'])) record['Network Topology'] = network_list # these are fileds that have comma sep strings entered by the users string_fields = [ 'Tags', 'Suggested Groups', 'Relies On', 'Suggested Areas of Work', 'Regional Traction', 'Suggested Values', 'Additions' ] for key in string_fields: record[key] = string_to_list(data_dict=record, key=key, delimiter=',') # remove contact and email for privacy del record['contact email'] del record['map contact'] # write out the files with open(f'projects/{label}.json', 'w') as outfile: json.dump(record, outfile) make_map_json() return "All files processed"
class FunctionalTest(unittest.TestCase): def setUp(self): super().setUp() # XXX Read the configuration from env variables. self.server_url = SERVER_URL self.auth = DEFAULT_AUTH # Read the configuration. self.config = configparser.RawConfigParser() self.config.read(os.path.join(__HERE__, "config/kinto.ini")) self.client = Client(server_url=self.server_url, auth=self.auth) self.create_user(self.auth) def tearDown(self): # Delete all the created objects flush_url = urljoin(self.server_url, "/__flush__") resp = requests.post(flush_url) resp.raise_for_status() def create_user(self, credentials): account_url = urljoin(self.server_url, "/accounts/{}".format(credentials[0])) r = requests.put(account_url, json={"data": { "password": credentials[1] }}, auth=DEFAULT_AUTH) r.raise_for_status() return r.json() def get_user_id(self, credentials): r = self.create_user(credentials) return "account:{}".format(r["data"]["id"]) def test_bucket_creation(self): bucket = self.client.create_bucket(id="mozilla") user_id = self.get_user_id(self.auth) assert user_id in bucket["permissions"]["write"] def test_bucket_creation_if_not_exists(self): self.client.create_bucket(id="mozilla") # Should not raise. self.client.create_bucket(id="mozilla", if_not_exists=True) def test_buckets_retrieval(self): self.client.create_bucket(id="mozilla") buckets = self.client.get_buckets() assert len(buckets) == 1 def test_bucket_retrieval(self): self.client.create_bucket(id="mozilla") self.client.get_bucket(id="mozilla") # XXX Add permissions handling during creation and check they are # present during retrieval. def test_bucket_modification(self): bucket = self.client.create_bucket(id="mozilla", data={"version": 1}) assert bucket["data"]["version"] == 1 bucket = self.client.patch_bucket(id="mozilla", data={"author": "you"}) assert bucket["data"]["version"] == 1 assert bucket["data"]["author"] == "you" bucket = self.client.update_bucket(id="mozilla", data={"date": "today"}) assert bucket["data"]["date"] == "today" assert "version" not in bucket["data"] def test_bucket_retrieval_fails_when_not_created(self): self.assertRaises(BucketNotFound, self.client.get_bucket, id="non-existent") def test_bucket_deletion(self): self.client.create_bucket(id="mozilla") self.client.delete_bucket(id="mozilla") self.assertRaises(BucketNotFound, self.client.get_bucket, id="mozilla") def test_bucket_deletion_if_exists(self): self.client.create_bucket(id="mozilla") self.client.delete_bucket(id="mozilla") self.client.delete_bucket(id="mozilla", if_exists=True) def test_buckets_deletion(self): self.client.create_bucket(id="mozilla") buckets = self.client.delete_buckets() assert buckets[0]["id"] == "mozilla" self.assertRaises(BucketNotFound, self.client.get_bucket, id="mozilla") def test_buckets_deletion_when_no_buckets_exist(self): deleted_buckets = self.client.delete_buckets() assert len(deleted_buckets) == 0 def test_bucket_save(self): self.client.create_bucket(id="mozilla", permissions={"write": ["account:alexis"]}) bucket = self.client.get_bucket(id="mozilla") assert "account:alexis" in bucket["permissions"]["write"] def test_group_creation(self): self.client.create_bucket(id="mozilla") self.client.create_group( id="payments", bucket="mozilla", data={"members": ["blah"]}, permissions={"write": ["blah"]}, ) # Test retrieval of a group gets the permissions as well. group = self.client.get_group(id="payments", bucket="mozilla") assert "blah" in group["permissions"]["write"] def test_group_creation_if_not_exists(self): self.client.create_bucket(id="mozilla") self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]}) self.client.create_group( id="payments", bucket="mozilla", data={"members": ["blah"]}, permissions={"write": ["blah"]}, if_not_exists=True, ) def test_group_creation_if_bucket_does_not_exist(self): with pytest.raises(KintoException) as e: self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]}) assert str(e).endswith( "PUT /v1/buckets/mozilla/groups/payments - " "403 Unauthorized. Please check that the " "bucket exists and that you have the permission " "to create or write on this group.") def test_group_update(self): self.client.create_bucket(id="mozilla") group = self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]}, if_not_exists=True) assert group["data"]["members"][0] == "blah" group = self.client.update_group(data={"members": ["blah", "foo"]}, id="payments", bucket="mozilla") self.assertEqual(group["data"]["members"][1], "foo") def test_group_list(self): self.client.create_bucket(id="mozilla") self.client.create_group(id="receipts", bucket="mozilla", data={"members": ["blah"]}) self.client.create_group(id="assets", bucket="mozilla", data={"members": ["blah"]}) # The returned groups should be strings. groups = self.client.get_groups(bucket="mozilla") self.assertEqual(2, len(groups)) self.assertEqual(set([coll["id"] for coll in groups]), set(["receipts", "assets"])) def test_group_deletion(self): self.client.create_bucket(id="mozilla") self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]}) self.client.delete_group(id="payments", bucket="mozilla") assert len(self.client.get_groups(bucket="mozilla")) == 0 def test_group_deletion_if_exists(self): self.client.create_bucket(id="mozilla") self.client.create_group(id="payments", bucket="mozilla", data={"members": ["blah"]}) self.client.delete_group(id="payments", bucket="mozilla") self.client.delete_group(id="payments", bucket="mozilla", if_exists=True) def test_group_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, "request", side_effect=error): with pytest.raises(KintoException): self.client.delete_group(id="payments", bucket="mozilla", if_exists=True) def test_groups_deletion(self): self.client.create_bucket(id="mozilla") self.client.create_group(id="amo", bucket="mozilla", data={"members": ["blah"]}) self.client.create_group(id="blocklist", bucket="mozilla", data={"members": ["blah"]}) self.client.delete_groups(bucket="mozilla") assert len(self.client.get_groups(bucket="mozilla")) == 0 def test_groups_deletion_when_no_groups_exist(self): self.client.create_bucket(id="mozilla") deleted_groups = self.client.delete_groups(bucket="mozilla") assert len(deleted_groups) == 0 def test_collection_creation(self): self.client.create_bucket(id="mozilla") self.client.create_collection( id="payments", bucket="mozilla", permissions={"write": ["account:alexis"]}) # Test retrieval of a collection gets the permissions as well. collection = self.client.get_collection(id="payments", bucket="mozilla") assert "account:alexis" in collection["permissions"]["write"] def test_collection_not_found(self): self.client.create_bucket(id="mozilla") with pytest.raises(CollectionNotFound): self.client.get_collection(id="payments", bucket="mozilla") def test_collection_access_forbidden(self): with pytest.raises(KintoException): self.client.get_collection(id="payments", bucket="mozilla") def test_collection_creation_if_not_exists(self): self.client.create_bucket(id="mozilla") self.client.create_collection(id="payments", bucket="mozilla") # Should not raise. self.client.create_collection(id="payments", bucket="mozilla", if_not_exists=True) def test_collection_list(self): self.client.create_bucket(id="mozilla") self.client.create_collection(id="receipts", bucket="mozilla") self.client.create_collection(id="assets", bucket="mozilla") # The returned collections should be strings. collections = self.client.get_collections(bucket="mozilla") self.assertEqual(len(collections), 2) self.assertEqual(set([coll["id"] for coll in collections]), set(["receipts", "assets"])) def test_collection_deletion(self): self.client.create_bucket(id="mozilla") self.client.create_collection(id="payments", bucket="mozilla") self.client.delete_collection(id="payments", bucket="mozilla") assert len(self.client.get_collections(bucket="mozilla")) == 0 def test_collection_deletion_if_exists(self): self.client.create_bucket(id="mozilla") self.client.create_collection(id="payments", bucket="mozilla") self.client.delete_collection(id="payments", bucket="mozilla") self.client.delete_collection(id="payments", bucket="mozilla", if_exists=True) def test_collection_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, "request", side_effect=error): with pytest.raises(KintoException): self.client.delete_collection(id="payments", bucket="mozilla", if_exists=True) def test_collections_deletion(self): self.client.create_bucket(id="mozilla") self.client.create_collection(id="amo", bucket="mozilla") self.client.create_collection(id="blocklist", bucket="mozilla") self.client.delete_collections(bucket="mozilla") assert len(self.client.get_collections(bucket="mozilla")) == 0 def test_collections_deletion_when_no_collections_exist(self): self.client.create_bucket(id="mozilla") deleted_collections = self.client.delete_collections(bucket="mozilla") assert len(deleted_collections) == 0 def test_record_creation_and_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() created = client.create_record( data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) record = client.get_record(id=created["data"]["id"]) assert "account:alexis" in record["permissions"]["read"] def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) records = client.get_records() assert len(records) == 1 def test_records_timestamp_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() record = client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) etag = client.get_records_timestamp() assert str(etag) == str(record["data"]["last_modified"]) def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10 def test_records_generator_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) pages = list(client.get_paginated_records()) assert len(pages) == 2 def test_single_record_save(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() created = client.create_record( data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) created["data"]["bar"] = "baz" # XXX enhance this in order to have to pass only one argument, created. client.update_record(id=created["data"]["id"], data=created["data"]) retrieved = client.get_record(id=created["data"]["id"]) assert "account:alexis" in retrieved["permissions"]["read"] assert retrieved["data"]["foo"] == u"bar" assert retrieved["data"]["bar"] == u"baz" assert created["data"]["id"] == retrieved["data"]["id"] def test_single_record_doesnt_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() created = client.create_record( data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) with self.assertRaises(KintoException): # Create a second record with the ID of the first one. client.create_record(data={ "id": created["data"]["id"], "bar": "baz" }) def test_single_record_creation_if_not_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() created = client.create_record(data={"foo": "bar"}) client.create_record(data={ "id": created["data"]["id"], "bar": "baz" }, if_not_exists=True) def test_single_record_can_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() created = client.create_record( data={"foo": "bar"}, permissions={"read": ["account:alexis"]}) client.create_record(data={ "id": created["data"]["id"], "bar": "baz" }, safe=False) def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() record = client.create_record(data={"foo": "bar"}) deleted = client.delete_record(id=record["data"]["id"]) assert deleted["deleted"] is True assert len(client.get_records()) == 0 def test_record_deletion_if_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() record = client.create_record(data={"foo": "bar"}) deleted = client.delete_record(id=record["data"]["id"]) deleted_if_exists = client.delete_record(id=record["data"]["id"], if_exists=True) assert deleted["deleted"] is True assert deleted_if_exists is None def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() client.create_record(data={"foo": "bar"}) client.delete_records() assert len(client.get_records()) == 0 def test_records_deletion_when_no_records_exist(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() deleted_records = client.delete_records() assert len(deleted_records) == 0 def test_bucket_sharing(self): alice_credentials = ("alice", "p4ssw0rd") alice_userid = self.get_user_id(alice_credentials) # Create a bucket and share it with alice. self.client.create_bucket(id="shared-bucket", permissions={"read": [alice_userid]}) alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_bucket(id="shared-bucket") def test_updating_data_on_a_group(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla") client.create_bucket() client.create_group(id="payments", data={"members": []}) client.patch_group(id="payments", data={"secret": "psssssst!"}) group = client.get_group(id="payments") assert group["data"]["secret"] == "psssssst!" def test_updating_data_on_a_collection(self): client = Client(server_url=self.server_url, auth=self.auth, bucket="mozilla", collection="payments") client.create_bucket() client.create_collection() client.patch_collection(data={"secret": "psssssst!"}) collection = client.get_collection() assert collection["data"]["secret"] == "psssssst!" def test_collection_sharing(self): alice_credentials = ("alice", "p4ssw0rd") alice_userid = self.get_user_id(alice_credentials) self.client.create_bucket(id="bob-bucket") self.client.create_collection(id="shared", bucket="bob-bucket", permissions={"read": [alice_userid]}) # Try to read the collection as Alice. alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_collection(id="shared", bucket="bob-bucket") def test_record_sharing(self): alice_credentials = ("alice", "p4ssw0rd") alice_userid = self.get_user_id(alice_credentials) # Create a record, and share it with Alice. self.client.create_bucket(id="bob-bucket") self.client.create_collection(id="bob-personal-collection", bucket="bob-bucket") record = self.client.create_record( data={"foo": "bar"}, permissions={"read": [alice_userid]}, bucket="bob-bucket", collection="bob-personal-collection", ) # Try to read the record as Alice alice_client = Client(server_url=self.server_url, auth=alice_credentials) record = alice_client.get_record(id=record["data"]["id"], bucket="bob-bucket", collection="bob-personal-collection") assert record["data"]["foo"] == "bar" def test_request_batching(self): with self.client.batch(bucket="mozilla", collection="fonts") as batch: batch.create_bucket() batch.create_collection() batch.create_record(data={"foo": "bar"}, permissions={"read": ["natim"]}) batch.create_record(data={"bar": "baz"}, permissions={"read": ["account:alexis"]}) _, _, r1, r2 = batch.results() records = self.client.get_records(bucket="mozilla", collection="fonts") assert len(records) == 2 assert records[0] == r2["data"] assert records[1] == r1["data"] def test_patch_record_jsonpatch(self): self.client.create_bucket(id="b1") self.client.create_collection(id="c1", bucket="b1") self.client.create_record(id="r1", collection="c1", bucket="b1", data={"hello": "world"}) patch = JSONPatch([ { "op": "add", "path": "/data/goodnight", "value": "moon" }, { "op": "add", "path": "/permissions/read/alice" }, ]) self.client.patch_record(id="r1", collection="c1", bucket="b1", changes=patch) record = self.client.get_record(bucket="b1", collection="c1", id="r1") assert record["data"]["hello"] == "world" assert record["data"]["goodnight"] == "moon" assert record["permissions"]["read"] == ["alice"] def test_replication(self): # First, create a few records on the first kinto collection. with self.client.batch(bucket="origin", collection="coll") as batch: batch.create_bucket() batch.create_collection() for n in range(10): batch.create_record(data={"foo": "bar", "n": n}) origin = Client(server_url=self.server_url, auth=self.auth, bucket="origin", collection="coll") destination = Client(server_url=self.server_url, auth=self.auth, bucket="destination", collection="coll") replication.replicate(origin, destination) records = self.client.get_records(bucket="destination", collection="coll") assert len(records) == 10
class FunctionalTest(unittest2.TestCase): def __init__(self, *args, **kwargs): super(FunctionalTest, self).__init__(*args, **kwargs) # XXX Read the configuration from env variables. self.server_url = SERVER_URL self.auth = DEFAULT_AUTH # Read the configuration. self.config = configparser.RawConfigParser() self.config.read(os.path.join(__HERE__, 'config/kinto.ini')) self.client = Client(server_url=self.server_url, auth=self.auth) def tearDown(self): # Delete all the created objects flush_url = urljoin(self.server_url, '/__flush__') resp = requests.post(flush_url) resp.raise_for_status() def get_user_id(self, credentials): hmac_secret = self.config.get('app:main', 'kinto.userid_hmac_secret') credentials = '%s:%s' % credentials digest = kinto_core_utils.hmac_digest(hmac_secret, credentials) return 'basicauth:%s' % digest def test_bucket_creation(self): bucket = self.client.create_bucket('mozilla') user_id = self.get_user_id(self.auth) assert user_id in bucket['permissions']['write'] def test_bucket_creation_if_not_exists(self): self.client.create_bucket('mozilla') # Should not raise. self.client.create_bucket('mozilla', if_not_exists=True) def test_buckets_retrieval(self): self.client.create_bucket('mozilla') buckets = self.client.get_buckets() assert len(buckets) == 1 def test_bucket_retrieval(self): self.client.create_bucket('mozilla') self.client.get_bucket('mozilla') # XXX Add permissions handling during creation and check they are # present during retrieval. def test_bucket_modification(self): bucket = self.client.create_bucket('mozilla', data={'version': 1}) assert bucket['data']['version'] == 1 bucket = self.client.patch_bucket('mozilla', data={'author': 'you'}) assert bucket['data']['version'] == 1 assert bucket['data']['author'] == 'you' bucket = self.client.update_bucket('mozilla', data={'date': 'today'}) assert bucket['data']['date'] == 'today' assert 'version' not in bucket['data'] def test_bucket_retrieval_fails_when_not_created(self): self.assertRaises(BucketNotFound, self.client.get_bucket, 'non-existent') def test_bucket_deletion(self): self.client.create_bucket('mozilla') self.client.delete_bucket('mozilla') self.assertRaises(BucketNotFound, self.client.get_bucket, 'mozilla') def test_bucket_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.delete_bucket('mozilla') self.client.delete_bucket('mozilla', if_exists=True) def test_buckets_deletion(self): self.client.create_bucket('mozilla') buckets = self.client.delete_buckets() assert buckets[0]['id'] == 'mozilla' self.assertRaises(BucketNotFound, self.client.get_bucket, 'mozilla') def test_buckets_deletion_when_no_buckets_exist(self): deleted_buckets = self.client.delete_buckets() assert len(deleted_buckets) == 0 def test_bucket_save(self): self.client.create_bucket('mozilla', permissions={'write': ['alexis']}) bucket = self.client.get_bucket('mozilla') assert 'alexis' in bucket['permissions']['write'] def test_group_creation(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}, permissions={'write': [ 'blah', ]}) # Test retrieval of a group gets the permissions as well. group = self.client.get_group('payments', bucket='mozilla') assert 'blah' in group['permissions']['write'] def test_group_creation_if_not_exists(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}, permissions={'write': [ 'blah', ]}, if_not_exists=True) def test_group_creation_if_bucket_does_not_exist(self): with pytest.raises(KintoException): self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}, if_not_exists=True) def test_group_update(self): self.client.create_bucket('mozilla') group = self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}, if_not_exists=True) assert group['data']['members'][0] == 'blah' group = self.client.update_group(data={'members': ['blah', 'foo']}, group='payments', bucket='mozilla') self.assertEquals(group['data']['members'][1], 'foo') def test_group_list(self): self.client.create_bucket('mozilla') self.client.create_group('receipts', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.create_group('assets', bucket='mozilla', data={'members': [ 'blah', ]}) # The returned groups should be strings. groups = self.client.get_groups('mozilla') self.assertEquals(2, len(groups)) self.assertEquals(set([coll['id'] for coll in groups]), set(['receipts', 'assets'])) def test_group_deletion(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.delete_group('payments', bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_group_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.delete_group('payments', bucket='mozilla') self.client.delete_group('payments', bucket='mozilla', if_exists=True) def test_group_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_group('payments', bucket='mozilla', if_exists=True) def test_groups_deletion(self): self.client.create_bucket('mozilla') self.client.create_group('amo', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.create_group('blocklist', bucket='mozilla', data={'members': [ 'blah', ]}) self.client.delete_groups(bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_groups_deletion_when_no_groups_exist(self): self.client.create_bucket('mozilla') deleted_groups = self.client.delete_groups(bucket='mozilla') assert len(deleted_groups) == 0 def test_collection_creation(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla', permissions={'write': [ 'alexis', ]}) # Test retrieval of a collection gets the permissions as well. collection = self.client.get_collection('payments', bucket='mozilla') assert 'alexis' in collection['permissions']['write'] def test_collection_creation_if_not_exists(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') # Should not raise. self.client.create_collection('payments', bucket='mozilla', if_not_exists=True) def test_collection_list(self): self.client.create_bucket('mozilla') self.client.create_collection('receipts', bucket='mozilla') self.client.create_collection('assets', bucket='mozilla') # The returned collections should be strings. collections = self.client.get_collections('mozilla') self.assertEquals(2, len(collections)) self.assertEquals(set([coll['id'] for coll in collections]), set(['receipts', 'assets'])) def test_collection_deletion(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collection_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla', if_exists=True) def test_collection_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_collection('payments', bucket='mozilla', if_exists=True) def test_collections_deletion(self): self.client.create_bucket('mozilla') self.client.create_collection('amo', bucket='mozilla') self.client.create_collection('blocklist', bucket='mozilla') self.client.delete_collections(bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collections_deletion_when_no_collections_exist(self): self.client.create_bucket('mozilla') deleted_collections = self.client.delete_collections(bucket='mozilla') assert len(deleted_collections) == 0 def test_record_creation_and_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) record = client.get_record(created['data']['id']) assert 'alexis' in record['permissions']['read'] def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) records = client.get_records() assert len(records) == 1 def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10 def test_single_record_save(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) created['data']['bar'] = 'baz' # XXX enhance this in order to have to pass only one argument, created. client.update_record(id=created['data']['id'], data=created['data']) retrieved = client.get_record(created['data']['id']) assert 'alexis' in retrieved['permissions']['read'] assert retrieved['data']['foo'] == u'bar' assert retrieved['data']['bar'] == u'baz' assert created['data']['id'] == retrieved['data']['id'] def test_single_record_doesnt_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) with self.assertRaises(KintoException): # Create a second record with the ID of the first one. client.create_record(data={ 'id': created['data']['id'], 'bar': 'baz' }) def test_single_record_creation_if_not_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}) client.create_record(data={ 'id': created['data']['id'], 'bar': 'baz' }, if_not_exists=True) def test_single_record_can_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) client.create_record(data={ 'id': created['data']['id'], 'bar': 'baz' }, safe=False) def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0 def test_record_deletion_if_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) deleted_if_exists = client.delete_record(record['data']['id'], if_exists=True) assert deleted['deleted'] is True assert deleted_if_exists is None def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record({'foo': 'bar'}) client.delete_records() assert len(client.get_records()) == 0 def test_records_deletion_when_no_records_exist(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() deleted_records = client.delete_records() assert len(deleted_records) == 0 def test_bucket_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a bucket and share it with alice. self.client.create_bucket('shared-bucket', permissions={'read': [ alice_userid, ]}) alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_bucket('shared-bucket') def test_updating_data_on_a_group(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla') client.create_bucket() client.create_group('payments', data={'members': []}) client.patch_group('payments', data={'secret': 'psssssst!'}) group = client.get_group('payments') assert group['data']['secret'] == 'psssssst!' def test_updating_data_on_a_collection(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.patch_collection(data={'secret': 'psssssst!'}) collection = client.get_collection() assert collection['data']['secret'] == 'psssssst!' def test_collection_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) self.client.create_bucket('bob-bucket') self.client.create_collection('shared', bucket='bob-bucket', permissions={'read': [ alice_userid, ]}) # Try to read the collection as Alice. alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_collection('shared', bucket='bob-bucket') def test_record_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a record, and share it with Alice. self.client.create_bucket('bob-bucket') self.client.create_collection('bob-personal-collection', bucket='bob-bucket') record = self.client.create_record( data={'foo': 'bar'}, permissions={'read': [ alice_userid, ]}, bucket='bob-bucket', collection='bob-personal-collection') # Try to read the record as Alice alice_client = Client(server_url=self.server_url, auth=alice_credentials) record = alice_client.get_record(id=record['data']['id'], bucket='bob-bucket', collection='bob-personal-collection') assert record['data']['foo'] == 'bar' def test_request_batching(self): with self.client.batch(bucket='mozilla', collection='fonts') as batch: batch.create_bucket() batch.create_collection() batch.create_record(data={'foo': 'bar'}, permissions={'read': ['natim']}) batch.create_record(data={'bar': 'baz'}, permissions={'read': ['alexis']}) records = self.client.get_records(bucket='mozilla', collection='fonts') assert len(records) == 2 def test_replication(self): # First, create a few records on the first kinto collection. with self.client.batch(bucket='origin', collection='coll') as batch: batch.create_bucket() batch.create_collection() for n in range(10): batch.create_record(data={'foo': 'bar', 'n': n}) origin = Client(server_url=self.server_url, auth=self.auth, bucket='origin', collection='coll') destination = Client(server_url=self.server_url, auth=self.auth, bucket='destination', collection='coll') replication.replicate(origin, destination) records = self.client.get_records(bucket='destination', collection='coll') assert len(records) == 10
class AppWindow(QtWidgets.QMainWindow, Ui_MainWindow): def __init__(self, parent=None): QtWidgets.QMainWindow.__init__(self, parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setupUi(self) ###################################################################################### ###################################################################################### self.settings = Settings() self.initRep() self.initKintoClient() self.syncRecipes() self.dlgEditG = Dialog(self) self.dlgEditH = DialogH(self) self.dlgEditD = DialogD(self) self.dlgEditY = DialogL(self) self.dlgPref = DialogPref(self) self.dlgStep = DialogStep(self) self.dlgMash = DialogMash(self) self.base = ImportBase() self.mashProfileExport = ExportMash() # self.base.importBeerXML() self.s = 0 self.recipe = None #Les connexions self.actionEnregistrer.triggered.connect(self.enregistrer) self.actionQuitter.triggered.connect(app.quit) # self.connect(self.actionQuitter, QtCore.SIGNAL("triggered()"), app, QtCore.SLOT("quit()")) self.actionShowJournal.triggered.connect(self.showJournal) self.actionEditGrains.triggered.connect(self.editGrains) self.actionEditHoublons.triggered.connect(self.editHoublons) self.actionEditDivers.triggered.connect(self.editDivers) self.actionEditLevures.triggered.connect(self.editLevures) self.actionRestaurerIngredients.triggered.connect(self.restoreDataBase) self.actionImportIng.triggered.connect(self.importIng) self.actionManageProfiles.triggered.connect(self.seeMash) self.actionAbout.triggered.connect(self.about) self.actionAllTools.triggered.connect(self.showTools) self.actionPreferences.triggered.connect(self.dialogPreferences) ####################################################################################################### # Profil de brassage ######################################################################################################### self.listWidgetSteps.itemSelectionChanged.connect(self.stepDetails) self.listWidgetMashProfiles.itemSelectionChanged.connect( self.mashClicked) self.buttonBoxMashDetails.rejected.connect(self.mashRejected) # self.comboBoxStepType.addItems(["Infusion", "Température", "Décoction"]) self.pushButtonStepEdit.clicked.connect(self.stepEdit) self.dlgStep.stepChanged.connect(self.stepReload) self.pushButtonStepRemove.clicked.connect(self.removeStep) self.pushButtonNewStep.clicked.connect(self.addStep) self.pushButtonMashEdit.clicked.connect(self.mashEdit) self.dlgMash.mashChanged.connect(self.mashReload) self.pushButtonNewProfile.clicked.connect(self.addMash) self.pushButtonRemoveProfile.clicked.connect(self.removeMash) self.pushButtonSaveProfile.clicked.connect(self.saveProfile) #La bibliotheque ################################################################################################################### ################################################################################################################### # self.listdir(recettes_dir) self.showLib() ################################################################################################### ######## gestion des arguments au lancement du programme ######################################### argumentsList = QtWidgets.QApplication.arguments() if len(argumentsList) > 1: logger.debug("la liste d'arguments: %s", argumentsList) logger.debug("le chemin: %s", argumentsList[1]) # for part in argumentsList : # recipePath=recipePath + " " + part try: recipePath = argumentsList[1] for part in argumentsList[2:]: recipePath = recipePath + " " + part self.openRecipeFile(recipePath) except: pass else: pass ######################################################################################################################## #################################################################################################################### # le signal émit à la fermeture de la fenêtre de préférences self.dlgPref.prefAccepted.connect(self.prefReload) ########################################################### ############### Journal ############################## ###################################################### def loadJournal(self): self.journal = Journal() self.journal.loadJournal() # self.actionEditJournal.setEnabled(True) @QtCore.pyqtSlot() def showJournal(self, entry=" '' "): self.stackedWidget.setCurrentIndex(0) self.loadJournal() pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBiblio.setHtml(self.journal.export("html", entry), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject( "main", self) # self.webViewBiblio.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) # self.webInspector = QtWebKit.QWebInspector(self) # self.webInspector.setPage(self.webViewBiblio.page()) # self.webInspector.setVisible(True) # self.verticalLayout_13.addWidget(self.webInspector) @QtCore.pyqtSlot(str, str) def addToJournal(self, event, recipeName): self.loadJournal() entry = '''{recipe:%s,date:%s,event:%s,editing:'True'} ''' % ( "'" + recipeName + "'", "'" + str(int(time.time())) + "'", "'" + self.journal.eventsLabels[event] + "'") self.showJournal(entry) @QtCore.pyqtSlot(str) def dumpJournal(self, journalJson): journalJson = '{"name":"journal","items": %s }' % journalJson d = json.loads(journalJson) with open(journal_file, mode="w", encoding="utf-8") as f: json.dump(d, f, indent=2) ############## Bibliothèque ############################## ########################################################## @QtCore.pyqtSlot() def showLib(self): # data = json.dumps(self.recipesSummary) # data = data.replace("'","'") self.stackedWidget.setCurrentIndex(0) self.brewdayLock = 0 self.webSettings = self.webViewBiblio.settings() self.webSettings.setAttribute( QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls, True) pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join( pyDir, "static/html/")) self.webViewBiblio.setHtml(LibExporterRepository['html'](), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject( "main", self) self.webViewBiblio.page().settings().setAttribute( QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) self.webViewBiblio.page().action( QtWebKitWidgets.QWebPage.Reload).setVisible(False) @QtCore.pyqtSlot(str) def deleteLib(self, path): confirmation = QtWidgets.QMessageBox.question( self, self.tr("Supprimer"), self.tr( "La recette sera définitivement supprimée <br/> Continuer ?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if (confirmation == QtWidgets.QMessageBox.Yes): os.remove(path) self.listdir(recettes_dir) self.showLib() else: self.showLib() @QtCore.pyqtSlot() def backWebViewBiblio(self): self.stackedWidget.setCurrentIndex(0) @QtCore.pyqtSlot(str, str) def saveRecipe(self, recipe, path): logger.debug(path) recipeFile = QtCore.QFile(path) if recipeFile.open(QtCore.QIODevice.WriteOnly): try: stream = QtCore.QTextStream(recipeFile) stream.setCodec("UTF-8") stream << recipe finally: recipeFile.close() else: # TODO : Prévenir l'utilisateur en cas d'échec de l'enregistrement pass @QtCore.pyqtSlot(result=str) def createPath(self, file_id=None): if file_id is None: file_id = str(int(time.time() * 10)) path = recettes_dir + "/" + file_id + ".xml" logger.debug(path) return path @QtCore.pyqtSlot() def resetLock(self): self.brewdayLock = 0 ############# Mode Brassage ################################ ############################################################ @QtCore.pyqtSlot(str) def showBrewdayMode(self, data): if self.brewdayLock == 0: self.stackedWidget.setCurrentIndex(1) self.brewdayLock = 1 data = data.replace("'", "'") pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBrewday.setHtml( BrewdayExporterRepository['html'](data), baseUrl) self.webViewBrewday.page().mainFrame().addToJavaScriptWindowObject( "main", self) self.webViewBrewday.page().settings().setAttribute( QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) self.webViewBrewday.page().action( QtWebKitWidgets.QWebPage.Reload).setVisible(False) else: self.stackedWidget.setCurrentIndex(1) ###### Outils ############################################ ########################################################## @QtCore.pyqtSlot() def showTools(self): self.stackedWidget.setCurrentIndex(0) pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBiblio.setHtml(ToolExporterRepository["html"](), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject( "main", self) # self.webViewBiblio.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) # self.webInspector = QtWebKit.QWebInspector(self) # self.webInspector.setPage(self.webViewBiblio.page()) # self.webInspector.setVisible(True) # self.verticalLayout_13.addWidget(self.webInspector) @QtCore.pyqtSlot(result=str) def dataRecipes(self): # f = open(recipeData_file, 'w') # f.write(self.recipesSummary) self.listdir(recettes_dir) return self.recipesSummary @QtCore.pyqtSlot(result=str) def dataProfiles(self): return self.mashProfileExport.exportJson(ImportBase().listeMashes) @QtCore.pyqtSlot(result=str) def dataIngredients(self): return ImportBase().exportjson() @QtCore.pyqtSlot(result=str) def dataPref(self): dic = {} dic["boilOffRate"] = settings.conf.value("BoilOffRate") dic["coolingLossRate"] = settings.conf.value("CoolingLoss") dic["grainTemp"] = settings.conf.value("GrainTemp") dic["fudgeFactor"] = settings.conf.value("FudgeFactor") dic["grainRetention"] = settings.conf.value("GrainRetention") dic = json.dumps(dic) return dic #Une fonction qui gère l'aperçu des couleurs. #Contient un tupple avec plusieurs références de couleurs, classées par rang selon la valeur SRM. ################################################################################################# # def colorPreview (self) : # self.colorTuppleSrm = ('FFE699', 'FFD878', 'FFCA5A', 'FFBF42', 'FBB123', 'F8A600', 'F39C00', 'EA8F00', 'E58500', 'DE7C00', 'D77200', 'CF6900', 'CB6200', 'C35900','BB5100', 'B54C00', 'B04500', 'A63E00', 'A13700', '9B3200', '952D00', '8E2900', '882300', '821E00', '7B1A00', '771900', '701400', '6A0E00', '660D00','5E0B00','5A0A02','600903', '520907', '4C0505', '470606', '440607', '3F0708', '3B0607', '3A070B', '36080A') # colorRef= round(self.recipe.compute_EBC()/1.97) # if colorRef >= 30 : # color = "#" + self.colorTuppleSrm[30] # elif colorRef <= 1 : # color = "#" + self.colorTuppleSrm[0] # else : # color = "#" + self.colorTuppleSrm[colorRef-1] # self.widgetColor.setStyleSheet("background-color :" + color) def liste_fichiers_recettes(self, rootdir): for root, subFolders, files in os.walk(rootdir): for file2 in files: yield (file2, os.path.join(root, file2)) def listdir(self, rootdir): summaries = [] for filename, recipe in self.liste_fichiers_recettes(rootdir): try: summaries.append(self.jsonRecipeLib(recipe)) except: logger.debug("le fichier %s n'est pas une recette" % (recipe)) self.recipesSummary = "[" + ",".join(summaries) + "]" logger.debug("%s recettes détectées" % (len(summaries))) def jsonRecipeLib(self, recipe): self.s = recipe self.recipe = Recipe.parse(recipe) data = self.recipe.export("json") data = data[1:-1] return data def initRep(self): home = QtCore.QDir(home_dir) config = QtCore.QDir(config_dir) logger.debug(config) if not config.exists(): home.mkpath(config_dir) else: pass database = QtCore.QFile(database_file) if not database.exists(): database.copy(database_root, database_file) else: pass recettes = QtCore.QFile(recettes_dir) if not recettes.exists(): try: shutil.copytree(samples_dir, samples_target) except: home.mkpath(recettes_dir) mash = QtCore.QFile(mash_file) if not mash.exists(): mash.copy(mash_root, mash_file) else: pass journal = QtCore.QFile(journal_file) if not journal.exists(): journal.copy(journal_root, journal_file) else: pass # on configure des valeurs par défaut if not settings.conf.contains("BoilOffRate"): settings.conf.setValue("BoilOffRate", 10) if not settings.conf.contains("CoolingLoss"): settings.conf.setValue("CoolingLoss", 5) if not settings.conf.contains("GrainTemp"): settings.conf.setValue("GrainTemp", 20) if not settings.conf.contains("FudgeFactor"): settings.conf.setValue("FudgeFactor", 1.7) if not settings.conf.contains("GrainRetention"): settings.conf.setValue("GrainRetention", 1) if not settings.conf.contains("Menus"): settings.conf.setValue("Menus", "button") def prefReload(self): if platform == 'win32': recettes_dir = settings.conf.value("pathWin32") else: recettes_dir = settings.conf.value("pathUnix") self.initRep() self.initKintoClient() self.listdir(recettes_dir) self.showLib() def initKintoClient(self): self.kinto_client = None kinto_url = settings.conf.value("KintoServerUrl") if kinto_url is not None and kinto_url != "": kinto_bucket = settings.conf.value("KintoDefaultBucket") kinto_credentials = (settings.conf.value("KintoBasicUserCred"), settings.conf.value("KintoBasicPasswordCred")) logger.debug(kinto_credentials) if kinto_bucket is None or kinto_bucket == "": kinto_bucket = "joliebulle" logger.debug("using default bucket 'joliebulle'") try: tmp_client = Client(server_url=kinto_url, auth=kinto_credentials) tmp_client.create_bucket(id=kinto_bucket, if_not_exists=True) self.kinto_client = Client(server_url=kinto_url, bucket=kinto_bucket, auth=kinto_credentials) # Création des collections self.kinto_client.create_collection(id='recipes', if_not_exists=True) self.kinto_client.create_collection(id='ingredients', if_not_exists=True) logger.info("Synchronize with kinto server at :" + repr(self.kinto_client)) except Exception as e: logger.warn("Failed to initialize Kinto synchronisation: " + repr(e)) def syncRecipes(self): recipes_collection = 'recipes' if self.kinto_client is None: return # Sync local recipes id_recettes_locales = [] for filename, full_filename in self.liste_fichiers_recettes( recettes_dir): recipe_id = filename.replace('.xml', '') id_recettes_locales.append(recipe_id) try: remote_recipe = self.kinto_client.get_record( id=recipe_id, collection=recipes_collection) logger.debug("recipe " + recipe_id + " already exists on server, update if needed") remote_timestamp = int(remote_recipe['data']['last_modified'] / 1000) local_timestamp = int(os.path.getmtime(full_filename)) logger.debug( str(remote_timestamp) + " " + str(local_timestamp)) if remote_timestamp < local_timestamp: logger.info("Mise à jour de la recette distante " + recipe_id) # La recette distant doit être mise à jour local_recipe = Recipe.parse(full_filename) data = local_recipe.export("dict") ret = self.kinto_client.update_record( id=recipe_id, collection=recipes_collection, data=data) new_timestamp = ret['data']['last_modified'] #MAJ du mtime du fichier pour marquer la synchronisation os.utime(full_filename, times=(int(new_timestamp / 1000), int(new_timestamp / 1000))) if remote_timestamp > local_timestamp: # La recette distante doit être mise à jour logger.info("Mise à jour de la recette locale " + recipe_id) new_recipe = Recipe.parse(remote_recipe['data'], "dict") self.doEnregistrerRecette(new_recipe, full_filename) os.utime(full_filename, times=(int(remote_timestamp), int(remote_timestamp))) except KintoException: logger.debug("recipe " + recipe_id + " doesn't exists on server, sending it") new_recipe = Recipe.parse(full_filename) data = new_recipe.export("dict") ret = self.kinto_client.create_record( id=recipe_id, collection=recipes_collection, data=data) new_timestamp = ret['data']['last_modified'] #MAJ du mtime du fichier pour marquer la synchronisation os.utime(full_filename, times=(int(new_timestamp / 1000), int(new_timestamp / 1000))) recipes = self.kinto_client.get_records(collection='recipes') for recipe in recipes: if recipe['id'] not in id_recettes_locales: #Recette distante non présente localement new_recipe = Recipe.parse(recipe, "dict") fullname = recipe['id'] + ".xml" logger.debug("Création de la recette " + fullname) self.doEnregistrerRecette(new_recipe, os.path.join(recettes_dir, fullname)) os.utime(full_filename, times=(int(remote_timestamp), int(remote_timestamp))) @QtCore.pyqtSlot() def switchToLibrary(self): self.stackedWidget.setCurrentIndex(0) # self.viewRecipeLib(self.s) def switchToMash(self): self.stackedWidget.setCurrentIndex(2) def restoreDataBase(self): home = QtCore.QDir(home_dir) config = QtCore.QDir(config_dir) database = QtCore.QFile(database_file) confirmation = QtWidgets.QMessageBox.question( self, self.tr("Remplacer la base ?"), self. tr("La base des ingrédients actuelle va être effacée et remplacée par la base originale. Toutes vos modifications vont être effacées. Un redémarrage de l'application sera nécessaire.<br> Continuer ?" ), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if (confirmation == QtWidgets.QMessageBox.Yes): database.remove(database_file) database.copy(database_root, database_file) else: pass def editGrains(self): self.dlgEditG.setModal(True) self.dlgEditG.setModel() self.dlgEditG.show() def editHoublons(self): self.dlgEditH.setModal(True) self.dlgEditH.setModel() self.dlgEditH.show() def editDivers(self): self.dlgEditD.setModal(True) self.dlgEditD.setModel() self.dlgEditD.show() def editLevures(self): self.dlgEditY.setModal(True) self.dlgEditY.setModel() self.dlgEditY.show() @QtCore.pyqtSlot(float, float, float, float) def preBoilCheck(self, volPreBoil, preBoilSg, GU, volume): self.dlgPreBoil = DialogPreBoil(self) self.dlgPreBoil.setData(volPreBoil, preBoilSg, GU, volume) self.dlgPreBoil.setModal(True) self.dlgPreBoil.show() def dialogPreferences(self): self.dlgPref.setModal(True) self.dlgPref.show() def importBeerXML(self): fichierBeerXML = self.s try: self.recipe = Recipe.parse(fichierBeerXML) self.currentRecipeMash = self.recipe.mash except: errors = Errors() errors.warningXml() def about(self): about = DialogAbout(self) about.show() def doEnregistrerRecette(self, recipe, destination): recipeFile = QtCore.QFile(destination) if recipeFile.open(QtCore.QIODevice.WriteOnly): try: stream = QtCore.QTextStream(recipeFile) stream.setCodec("UTF-8") stream << recipe.export("beerxml") finally: recipeFile.close() else: # TODO : Prévenir l'utilisateur en cas d'échec de l'enregistrement pass def enregistrerRecette(self, destination): self.doEnregistrerRecette(self.recipe, destination) self.fileSaved = True def enregistrer(self): if self.recipe.name != self.lineEditRecette.text(): self.nameChanged = True else: self.nameChanged = False self.recipe.name = self.lineEditRecette.text() self.recipe.style = self.lineEditGenre.text() self.recipe.brewer = self.lineEditBrewer.text() self.recipe.boil = self.spinBoxBoil.value() if not self.s: destination = recettes_dir + "/" + self.recipe.name.replace( '/', ' ') + ".xml" if os.path.exists(destination): errors = Errors() errors.warningExistingPath() self.fileSaved = False else: self.s = destination self.enregistrerRecette(destination) else: self.enregistrerRecette(self.s) def enregistrerSous(self): self.s = QtGui.QFileDialog.getSaveFileName( self, self.tr("Enregistrer dans un fichier"), recettes_dir + "/" + self.recipe.name.replace('/', ' ') + ".xml", "BeerXML (*.xml)") self.enregistrerRecette(self.s) @QtCore.pyqtSlot(str) def copyBbcode(self, bbcode): app.clipboard().setText(bbcode) def importIng(self): s = QtWidgets.QFileDialog.getOpenFileName( self, self.tr("Ouvrir un fichier"), home_dir, ) if not s: pass else: self.importIngList = ImportIng() self.importIngList.parseFile(s) def mashComboChanged(self): #on remet le verrou à 0, il va falloir recalculer en repassant en brewday mode self.brewdayLock = 0 try: i = self.comboBoxMashProfiles.currentIndex() self.currentMash = ImportBase().listeMashes[i] except: self.currentMash = self.currentRecipeMash if i == -1: self.currentMash = Mash() self.recipe.mash = self.currentMash def seeMash(self): self.switchToMash() index = self.listWidgetMashProfiles.currentRow() i = self.listWidgetSteps.currentRow() self.listWidgetMashProfiles.clear() self.listWidgetSteps.clear() self.numMash = len(ImportBase().listeMashes) #self.numSteps = self.mashProfilesBase.numSteps self.popMashList() self.pushButtonMashEdit.setEnabled(False) self.pushButtonRemoveProfile.setEnabled(False) self.pushButtonStepRemove.setEnabled(False) self.pushButtonStepEdit.setEnabled(False) self.listWidgetMashProfiles.setCurrentRow(index) self.listWidgetSteps.setCurrentRow(i) def popMashList(self): self.listWidgetMashProfiles.clear() for mash in ImportBase().listeMashes: self.listWidgetMashProfiles.addItem(mash.name) def mashClicked(self): self.listWidgetSteps.clear() index = self.listWidgetMashProfiles.currentRow() if index > -1: mash = ImportBase().listeMashes[index] for step in mash.listeSteps: self.listWidgetSteps.addItem(step.name) self.labelStepName.setTextFormat(QtCore.Qt.RichText) self.labelMashName.setText("<b>" + mash.name + "</b>") self.labelMashPh.setText("%.1f" % float(mash.ph)) # self.labelMashGrainTemp.setText("%.1f" %float(self.dicMashDetail['grainTemp'])) # self.labelMashTunTemp.setText("%.1f" %float(self.dicMashDetail['tunTemp'])) try: self.labelMashSpargeTemp.setText("%.1f" % float(mash.spargeTemp)) except: pass try: self.listWidgetSteps.setCurrentRow(0) except: pass # print(self.dicMashDetail) self.pushButtonMashEdit.setEnabled(True) self.pushButtonRemoveProfile.setEnabled(True) def mashDetails(self): self.dlgMashDetail = DialogMashDetail(self) self.dlgMashDetail.setModal(True) self.dlgMashDetail.show() self.dlgMashDetail.setFields(self.currentMash) self.dlgMashDetail.setAttribute(QtCore.Qt.WA_DeleteOnClose, True) def stepDetails(self): index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: try: selected_step = selected_mash.listeSteps[i] self.labelStepName.setTextFormat(QtCore.Qt.RichText) self.labelStepName.setText("<b>" + selected_step.name + "</b>") self.labelStepType.setText(selected_step.type) self.labelStepTemp.setText( MashStepView.temp_to_display(selected_step.temp)) self.labelStepTime.setText( MashStepView.time_to_display(selected_step.time)) self.pushButtonStepRemove.setEnabled(True) self.pushButtonStepEdit.setEnabled(True) except: pass def stepEdit(self): index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: selected_step = selected_mash.listeSteps[i] self.dlgStep.show() self.dlgStep.fields(selected_step) def stepReload(self, step): index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: selected_step = selected_mash.listeSteps[i] selected_step.name = step.name selected_step.type = step.type selected_step.temp = step.temp selected_step.time = step.time self.seeMash() self.stepDetails() self.listWidgetMashProfiles.setCurrentRow(index) self.listWidgetSteps.setCurrentRow(i) def removeStep(self): index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: item = self.listWidgetSteps.currentItem() del selected_mash.listeSteps[i] # self.listWidgetSteps.clearSelection() #self.listWidgetSteps.takeItem(item) #On force la sélection sur la ligne précédente self.listWidgetSteps.setCurrentRow(i - 1) self.seeMash() def addStep(self): index = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() step = MashStep() step.name = 'Nouveau palier' step.type = 'Infusion' step.time = '0' step.temp = '0' step.vol = '0' selected_mash.listeSteps.append(step) self.listWidgetMashProfiles.setCurrentRow(index) self.seeMash() self.stepDetails() self.listWidgetMashProfiles.setCurrentRow(index) # self.listWidgetSteps.setCurrentRow(i-1) # self.stepEdit() def mashEdit(self): index = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[index] self.dlgMash.show() self.dlgMash.fields(selected_mash) def mashReload(self, mash): #on remet le verrou à 0, il va falloir recalculer en repassant en brewday mode self.brewdayLock = 0 f = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[f] selected_mash.name = mash.name selected_mash.ph = mash.ph selected_mash.grainTemp = 20 selected_mash.tunTemp = 20 selected_mash.spargeTemp = mash.spargeTemp self.popMashList() self.listWidgetMashProfiles.setCurrentRow(f) def addMash(self): new_mash = Mash() new_mash.name = 'Nouveau profil' new_mash.grainTemp = '0' new_mash.tunTemp = '0' new_mash.spargeTemp = '78' new_mash.ph = 5.4 new_step = MashStep() new_step.name = 'Nouveau Palier' new_step.type = 'Infusion' new_step.time = '0' new_step.temp = '0' new_mash.listeSteps.append(new_step) ImportBase().listeMashes.append(new_mash) self.seeMash() self.listWidgetMashProfiles.setCurrentRow( len(ImportBase().listeMashes) - 1) def removeMash(self): i = self.listWidgetMashProfiles.currentRow() del ImportBase().listeMashes[i] self.seeMash() self.listWidgetSteps.clear() def mashRejected(self): self.showLib() def saveProfile(self): self.mashProfileExport.export(ImportBase().listeMashes) self.mashProfileExport.enregistrer(mash_file) @QtCore.pyqtSlot() def printRecipe(self): printer = QtPrintSupport.QPrinter() dialog = QtPrintSupport.QPrintDialog(printer) dialog.setModal(True) dialog.setWindowTitle("Print Document") if dialog.exec_() == True: self.webViewBiblio.print(printer) # document=QtGui.QTextDocument() # stringHtml=self.recipe.export("print") # document.setHtml(stringHtml) # document.print(printer) @QtCore.pyqtSlot() def printBrewday(self): printer = QtPrintSupport.QPrinter() dialog = QtPrintSupport.QPrintDialog(printer) dialog.setModal(True) dialog.setWindowTitle("Print Document") if dialog.exec_() == True: self.webViewBrewday.print(printer)
def main(): args = _get_args() client = Client(server_url=args.server, auth=tuple(args.auth.split(':')), bucket=args.source_bucket, collection=args.source_col) if args.editor_auth is None: args.editor_auth = args.auth if args.reviewer_auth is None: args.reviewer_auth = args.auth editor_client = Client(server_url=args.server, auth=tuple(args.editor_auth.split(':')), bucket=args.source_bucket, collection=args.source_col) reviewer_client = Client(server_url=args.server, auth=tuple(args.reviewer_auth.split(':')), bucket=args.source_bucket, collection=args.source_col) # 0. initialize source bucket/collection (if necessary) server_info = client.server_info() editor_id = editor_client.server_info()['user']['id'] reviewer_id = reviewer_client.server_info()['user']['id'] print('Server: {0}'.format(args.server)) print('Author: {user[id]}'.format(**server_info)) print('Editor: {0}'.format(editor_id)) print('Reviewer: {0}'.format(reviewer_id)) # 0. check that this collection is well configured. signer_capabilities = server_info['capabilities']['signer'] to_review_enabled = signer_capabilities.get('to_review_enabled', False) group_check_enabled = signer_capabilities.get('group_check_enabled', False) resources = [ r for r in signer_capabilities['resources'] if (args.source_bucket, args.source_col) == (r['source']['bucket'], r['source']['collection']) ] assert len(resources) > 0, 'Specified source not configured to be signed' resource = resources[0] if to_review_enabled and 'preview' in resource: print( 'Signoff: {source[bucket]}/{source[collection]} => {preview[bucket]}/{preview[collection]} => {destination[bucket]}/{destination[collection]}' .format(**resource)) else: print( 'Signoff: {source[bucket]}/{source[collection]} => {destination[bucket]}/{destination[collection]}' .format(**resource)) print('Group check: {0}'.format(group_check_enabled)) print('Review workflow: {0}'.format(to_review_enabled)) print('_' * 80) bucket = client.create_bucket(if_not_exists=True) client.patch_bucket(permissions={ 'write': [editor_id, reviewer_id] + bucket['permissions']['write'] }, if_match=bucket['data']['last_modified'], safe=True) client.create_collection(if_not_exists=True) if args.reset: client.delete_records() existing = 0 else: existing_records = client.get_records() existing = len(existing_records) if group_check_enabled: editors_group = signer_capabilities['editors_group'] client.create_group(editors_group, data={'members': [editor_id]}, if_not_exists=True) reviewers_group = signer_capabilities['reviewers_group'] client.create_group(reviewers_group, data={'members': [reviewer_id]}, if_not_exists=True) dest_client = Client(server_url=args.server, bucket=resource['destination']['bucket'], collection=resource['destination']['collection']) preview_client = None if to_review_enabled and 'preview' in resource: preview_bucket = resource['preview']['bucket'] preview_collection = resource['preview']['collection'] preview_client = Client(server_url=args.server, bucket=preview_bucket, collection=preview_collection) # 1. upload data print('Author uploads 20 random records') records = upload_records(client, 20) # 2. ask for a signature # 2.1 ask for review (noop on old versions) print('Editor asks for review') data = {"status": "to-review"} editor_client.patch_collection(data=data) # 2.2 check the preview collection (if enabled) if preview_client: print('Check preview collection') preview_records = preview_client.get_records() expected = existing + 20 assert len(preview_records) == expected, '%s != %s records' % ( len(preview_records), expected) metadata = preview_client.get_collection()['data'] preview_signature = metadata.get('signature') assert preview_signature, 'Preview collection not signed' preview_timestamp = collection_timestamp(preview_client) # 2.3 approve the review print('Reviewer approves and triggers signature') data = {"status": "to-sign"} reviewer_client.patch_collection(data=data) # 3. upload more data print('Author creates 20 others records') upload_records(client, 20) print('Editor updates 5 random records') for toupdate in random.sample(records, 5): editor_client.patch_record(dict(newkey=_rand(10), **toupdate)) print('Author deletes 5 random records') for todelete in random.sample(records, 5): client.delete_record(todelete['id']) expected = existing + 20 + 20 - 5 # 4. ask again for a signature # 2.1 ask for review (noop on old versions) print('Editor asks for review') data = {"status": "to-review"} editor_client.patch_collection(data=data) # 2.2 check the preview collection (if enabled) if preview_client: print('Check preview collection') preview_records = preview_client.get_records() assert len(preview_records) == expected, '%s != %s records' % ( len(preview_records), expected) # Diff size is 20 + 5 if updated records are also all deleted, # or 30 if deletions and updates apply to different records. diff_since_last = preview_client.get_records(_since=preview_timestamp) assert 25 <= len( diff_since_last ) <= 30, 'Changes since last signature are not consistent' metadata = preview_client.get_collection()['data'] assert preview_signature != metadata[ 'signature'], 'Preview collection not updated' # 2.3 approve the review print('Reviewer approves and triggers signature') data = {"status": "to-sign"} reviewer_client.patch_collection(data=data) # 5. wait for the result # 6. obtain the destination records and serialize canonically. records = list(dest_client.get_records()) assert len(records) == expected, '%s != %s records' % (len(records), expected) timestamp = collection_timestamp(dest_client) serialized = canonical_json(records, timestamp) print('Hash is %r' % compute_hash(serialized)) # 7. get back the signed hash dest_col = dest_client.get_collection() signature = dest_col['data']['signature'] with open('pub', 'w') as f: f.write(signature['public_key']) # 8. verify the signature matches the hash signer = ECDSASigner(public_key='pub') try: signer.verify(serialized, signature) print('Signature OK') except Exception: print('Signature KO') raise
class FunctionalTest(unittest.TestCase): def setUp(self): super().setUp() # XXX Read the configuration from env variables. self.server_url = SERVER_URL self.auth = DEFAULT_AUTH # Read the configuration. self.config = configparser.RawConfigParser() self.config.read(os.path.join(__HERE__, 'config/kinto.ini')) self.client = Client(server_url=self.server_url, auth=self.auth) self.create_user(self.auth) def tearDown(self): # Delete all the created objects flush_url = urljoin(self.server_url, '/__flush__') resp = requests.post(flush_url) resp.raise_for_status() def create_user(self, credentials): account_url = urljoin(self.server_url, '/accounts/{}'.format(credentials[0])) r = requests.put(account_url, json={"data": {"password": credentials[1]}}, auth=DEFAULT_AUTH) r.raise_for_status() return r.json() def get_user_id(self, credentials): r = self.create_user(credentials) return 'account:{}'.format(r["data"]["id"]) def test_bucket_creation(self): bucket = self.client.create_bucket(id='mozilla') user_id = self.get_user_id(self.auth) assert user_id in bucket['permissions']['write'] def test_bucket_creation_if_not_exists(self): self.client.create_bucket(id='mozilla') # Should not raise. self.client.create_bucket(id='mozilla', if_not_exists=True) def test_buckets_retrieval(self): self.client.create_bucket(id='mozilla') buckets = self.client.get_buckets() assert len(buckets) == 1 def test_bucket_retrieval(self): self.client.create_bucket(id='mozilla') self.client.get_bucket(id='mozilla') # XXX Add permissions handling during creation and check they are # present during retrieval. def test_bucket_modification(self): bucket = self.client.create_bucket(id='mozilla', data={'version': 1}) assert bucket['data']['version'] == 1 bucket = self.client.patch_bucket(id='mozilla', data={'author': 'you'}) assert bucket['data']['version'] == 1 assert bucket['data']['author'] == 'you' bucket = self.client.update_bucket(id='mozilla', data={'date': 'today'}) assert bucket['data']['date'] == 'today' assert 'version' not in bucket['data'] def test_bucket_retrieval_fails_when_not_created(self): self.assertRaises(BucketNotFound, self.client.get_bucket, id='non-existent') def test_bucket_deletion(self): self.client.create_bucket(id='mozilla') self.client.delete_bucket(id='mozilla') self.assertRaises(BucketNotFound, self.client.get_bucket, id='mozilla') def test_bucket_deletion_if_exists(self): self.client.create_bucket(id='mozilla') self.client.delete_bucket(id='mozilla') self.client.delete_bucket(id='mozilla', if_exists=True) def test_buckets_deletion(self): self.client.create_bucket(id='mozilla') buckets = self.client.delete_buckets() assert buckets[0]['id'] == 'mozilla' self.assertRaises(BucketNotFound, self.client.get_bucket, id='mozilla') def test_buckets_deletion_when_no_buckets_exist(self): deleted_buckets = self.client.delete_buckets() assert len(deleted_buckets) == 0 def test_bucket_save(self): self.client.create_bucket(id='mozilla', permissions={'write': ['account:alexis']}) bucket = self.client.get_bucket(id='mozilla') assert 'account:alexis' in bucket['permissions']['write'] def test_group_creation(self): self.client.create_bucket(id='mozilla') self.client.create_group( id='payments', bucket='mozilla', data={'members': ['blah', ]}, permissions={'write': ['blah', ]}) # Test retrieval of a group gets the permissions as well. group = self.client.get_group(id='payments', bucket='mozilla') assert 'blah' in group['permissions']['write'] def test_group_creation_if_not_exists(self): self.client.create_bucket(id='mozilla') self.client.create_group(id='payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group( id='payments', bucket='mozilla', data={'members': ['blah', ]}, permissions={'write': ['blah', ]}, if_not_exists=True) def test_group_creation_if_bucket_does_not_exist(self): with pytest.raises(KintoException) as e: self.client.create_group( id='payments', bucket='mozilla', data={'members': ['blah']}) assert str(e).endswith('PUT /v1/buckets/mozilla/groups/payments - ' '403 Unauthorized. Please check that the ' 'bucket exists and that you have the permission ' 'to create or write on this group.') def test_group_update(self): self.client.create_bucket(id='mozilla') group = self.client.create_group( id='payments', bucket='mozilla', data={'members': ['blah', ]}, if_not_exists=True) assert group['data']['members'][0] == 'blah' group = self.client.update_group( data={'members': ['blah', 'foo']}, id='payments', bucket='mozilla') self.assertEqual(group['data']['members'][1], 'foo') def test_group_list(self): self.client.create_bucket(id='mozilla') self.client.create_group(id='receipts', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group(id='assets', bucket='mozilla', data={'members': ['blah', ]}) # The returned groups should be strings. groups = self.client.get_groups(bucket='mozilla') self.assertEqual(2, len(groups)) self.assertEqual(set([coll['id'] for coll in groups]), set(['receipts', 'assets'])) def test_group_deletion(self): self.client.create_bucket(id='mozilla') self.client.create_group(id='payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_group(id='payments', bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_group_deletion_if_exists(self): self.client.create_bucket(id='mozilla') self.client.create_group(id='payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_group(id='payments', bucket='mozilla') self.client.delete_group(id='payments', bucket='mozilla', if_exists=True) def test_group_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_group(id='payments', bucket='mozilla', if_exists=True) def test_groups_deletion(self): self.client.create_bucket(id='mozilla') self.client.create_group(id='amo', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group(id='blocklist', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_groups(bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_groups_deletion_when_no_groups_exist(self): self.client.create_bucket(id='mozilla') deleted_groups = self.client.delete_groups(bucket='mozilla') assert len(deleted_groups) == 0 def test_collection_creation(self): self.client.create_bucket(id='mozilla') self.client.create_collection( id='payments', bucket='mozilla', permissions={'write': ['account:alexis', ]} ) # Test retrieval of a collection gets the permissions as well. collection = self.client.get_collection(id='payments', bucket='mozilla') assert 'account:alexis' in collection['permissions']['write'] def test_collection_not_found(self): self.client.create_bucket(id='mozilla') with pytest.raises(CollectionNotFound): self.client.get_collection(id='payments', bucket='mozilla') def test_collection_access_forbidden(self): with pytest.raises(KintoException): self.client.get_collection(id='payments', bucket='mozilla') def test_collection_creation_if_not_exists(self): self.client.create_bucket(id='mozilla') self.client.create_collection(id='payments', bucket='mozilla') # Should not raise. self.client.create_collection(id='payments', bucket='mozilla', if_not_exists=True) def test_collection_list(self): self.client.create_bucket(id='mozilla') self.client.create_collection(id='receipts', bucket='mozilla') self.client.create_collection(id='assets', bucket='mozilla') # The returned collections should be strings. collections = self.client.get_collections(bucket='mozilla') self.assertEqual(len(collections), 2) self.assertEqual(set([coll['id'] for coll in collections]), set(['receipts', 'assets'])) def test_collection_deletion(self): self.client.create_bucket(id='mozilla') self.client.create_collection(id='payments', bucket='mozilla') self.client.delete_collection(id='payments', bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collection_deletion_if_exists(self): self.client.create_bucket(id='mozilla') self.client.create_collection(id='payments', bucket='mozilla') self.client.delete_collection(id='payments', bucket='mozilla') self.client.delete_collection(id='payments', bucket='mozilla', if_exists=True) def test_collection_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_collection(id='payments', bucket='mozilla', if_exists=True) def test_collections_deletion(self): self.client.create_bucket(id='mozilla') self.client.create_collection(id='amo', bucket='mozilla') self.client.create_collection(id='blocklist', bucket='mozilla') self.client.delete_collections(bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collections_deletion_when_no_collections_exist(self): self.client.create_bucket(id='mozilla') deleted_collections = self.client.delete_collections(bucket='mozilla') assert len(deleted_collections) == 0 def test_record_creation_and_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) record = client.get_record(id=created['data']['id']) assert 'account:alexis' in record['permissions']['read'] def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) records = client.get_records() assert len(records) == 1 def test_records_timestamp_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) etag = client.get_records_timestamp() assert str(etag) == str(record["data"]["last_modified"]) def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10 def test_single_record_save(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) created['data']['bar'] = 'baz' # XXX enhance this in order to have to pass only one argument, created. client.update_record(id=created['data']['id'], data=created['data']) retrieved = client.get_record(id=created['data']['id']) assert 'account:alexis' in retrieved['permissions']['read'] assert retrieved['data']['foo'] == u'bar' assert retrieved['data']['bar'] == u'baz' assert created['data']['id'] == retrieved['data']['id'] def test_single_record_doesnt_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) with self.assertRaises(KintoException): # Create a second record with the ID of the first one. client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}) def test_single_record_creation_if_not_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}) client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}, if_not_exists=True) def test_single_record_can_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['account:alexis']}) client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}, safe=False) def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record(data={'foo': 'bar'}) deleted = client.delete_record(id=record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0 def test_record_deletion_if_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record(data={'foo': 'bar'}) deleted = client.delete_record(id=record['data']['id']) deleted_if_exists = client.delete_record(id=record['data']['id'], if_exists=True) assert deleted['deleted'] is True assert deleted_if_exists is None def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}) client.delete_records() assert len(client.get_records()) == 0 def test_records_deletion_when_no_records_exist(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() deleted_records = client.delete_records() assert len(deleted_records) == 0 def test_bucket_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a bucket and share it with alice. self.client.create_bucket(id='shared-bucket', permissions={'read': [alice_userid, ]}) alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_bucket(id='shared-bucket') def test_updating_data_on_a_group(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla') client.create_bucket() client.create_group(id='payments', data={'members': []}) client.patch_group(id='payments', data={'secret': 'psssssst!'}) group = client.get_group(id='payments') assert group['data']['secret'] == 'psssssst!' def test_updating_data_on_a_collection(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.patch_collection(data={'secret': 'psssssst!'}) collection = client.get_collection() assert collection['data']['secret'] == 'psssssst!' def test_collection_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) self.client.create_bucket(id='bob-bucket') self.client.create_collection( id='shared', bucket='bob-bucket', permissions={'read': [alice_userid, ]}) # Try to read the collection as Alice. alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_collection(id='shared', bucket='bob-bucket') def test_record_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a record, and share it with Alice. self.client.create_bucket(id='bob-bucket') self.client.create_collection(id='bob-personal-collection', bucket='bob-bucket') record = self.client.create_record( data={'foo': 'bar'}, permissions={'read': [alice_userid, ]}, bucket='bob-bucket', collection='bob-personal-collection') # Try to read the record as Alice alice_client = Client(server_url=self.server_url, auth=alice_credentials) record = alice_client.get_record( id=record['data']['id'], bucket='bob-bucket', collection='bob-personal-collection') assert record['data']['foo'] == 'bar' def test_request_batching(self): with self.client.batch(bucket='mozilla', collection='fonts') as batch: batch.create_bucket() batch.create_collection() batch.create_record(data={'foo': 'bar'}, permissions={'read': ['natim']}) batch.create_record(data={'bar': 'baz'}, permissions={'read': ['account:alexis']}) _, _, r1, r2 = batch.results() records = self.client.get_records(bucket='mozilla', collection='fonts') assert len(records) == 2 assert records[0] == r2['data'] assert records[1] == r1['data'] def test_patch_record_jsonpatch(self): self.client.create_bucket(id='b1') self.client.create_collection(id='c1', bucket='b1') self.client.create_record(id='r1', collection='c1', bucket='b1', data={"hello": "world"}) patch = JSONPatch([ {'op': 'add', 'path': '/data/goodnight', 'value': 'moon'}, {'op': 'add', 'path': '/permissions/read/alice'} ]) self.client.patch_record(id='r1', collection='c1', bucket='b1', changes=patch) record = self.client.get_record(bucket='b1', collection='c1', id='r1') assert record['data']['hello'] == 'world' assert record['data']['goodnight'] == 'moon' assert record['permissions']['read'] == ['alice'] def test_replication(self): # First, create a few records on the first kinto collection. with self.client.batch(bucket='origin', collection='coll') as batch: batch.create_bucket() batch.create_collection() for n in range(10): batch.create_record(data={'foo': 'bar', 'n': n}) origin = Client( server_url=self.server_url, auth=self.auth, bucket='origin', collection='coll' ) destination = Client( server_url=self.server_url, auth=self.auth, bucket='destination', collection='coll') replication.replicate(origin, destination) records = self.client.get_records(bucket='destination', collection='coll') assert len(records) == 10
def main(): args = _get_args() client = Client(server_url=args.server, auth=tuple(args.auth.split(':')), bucket=args.source_bucket, collection=args.source_col) if args.editor_auth is None: args.editor_auth = args.auth if args.reviewer_auth is None: args.reviewer_auth = args.auth editor_client = Client(server_url=args.server, auth=tuple(args.editor_auth.split(':')), bucket=args.source_bucket, collection=args.source_col) reviewer_client = Client(server_url=args.server, auth=tuple(args.reviewer_auth.split(':')), bucket=args.source_bucket, collection=args.source_col) # 0. initialize source bucket/collection (if necessary) server_info = client.server_info() editor_id = editor_client.server_info()['user']['id'] reviewer_id = reviewer_client.server_info()['user']['id'] print('Server: {0}'.format(args.server)) print('Author: {user[id]}'.format(**server_info)) print('Editor: {0}'.format(editor_id)) print('Reviewer: {0}'.format(reviewer_id)) # 0. check that this collection is well configured. signer_capabilities = server_info['capabilities']['signer'] resources = [r for r in signer_capabilities['resources'] if (args.source_bucket, args.source_col) == (r['source']['bucket'], r['source']['collection']) or (args.source_bucket, None) == (r['source']['bucket'], r['source']['collection'])] assert len(resources) > 0, 'Specified source not configured to be signed' resource = resources[0] if 'preview' in resource: print('Signoff: {source[bucket]}/{source[collection]} => {preview[bucket]}/{preview[collection]} => {destination[bucket]}/{destination[collection]}'.format(**resource)) else: print('Signoff: {source[bucket]}/{source[collection]} => {destination[bucket]}/{destination[collection]}'.format(**resource)) print('_' * 80) bucket = client.create_bucket(if_not_exists=True) client.create_collection(permissions={'write': [editor_id, reviewer_id] + bucket['permissions']['write']}, if_not_exists=True) editors_group = resource.get('editors_group') or signer_capabilities['editors_group'] editors_group = editors_group.format(collection_id=args.source_col) client.patch_group(id=editors_group, data={'members': [editor_id]}) reviewers_group = resource.get('reviewers_group') or signer_capabilities['reviewers_group'] reviewers_group = reviewers_group.format(collection_id=args.source_col) client.patch_group(id=reviewers_group, data={'members': [reviewer_id]}) if args.reset: client.delete_records() existing = 0 else: existing_records = client.get_records() existing = len(existing_records) dest_col = resource['destination'].get('collection') or args.source_col dest_client = Client(server_url=args.server, bucket=resource['destination']['bucket'], collection=dest_col) preview_client = None if 'preview' in resource: preview_bucket = resource['preview']['bucket'] preview_collection = resource['preview'].get('collection') or args.source_col preview_client = Client(server_url=args.server, bucket=preview_bucket, collection=preview_collection) # 1. upload data print('Author uploads 20 random records') records = upload_records(client, 20) # 2. ask for a signature # 2.1 ask for review (noop on old versions) print('Editor asks for review') data = {"status": "to-review"} editor_client.patch_collection(data=data) # 2.2 check the preview collection (if enabled) if preview_client: print('Check preview collection') preview_records = preview_client.get_records() expected = existing + 20 assert len(preview_records) == expected, '%s != %s records' % (len(preview_records), expected) metadata = preview_client.get_collection()['data'] preview_signature = metadata.get('signature') assert preview_signature, 'Preview collection not signed' preview_timestamp = preview_client.get_records_timestamp() # 2.3 approve the review print('Reviewer approves and triggers signature') data = {"status": "to-sign"} reviewer_client.patch_collection(data=data) # 3. upload more data print('Author creates 20 others records') upload_records(client, 20) print('Editor updates 5 random records') for toupdate in random.sample(records, 5): editor_client.patch_record(data=dict(newkey=_rand(10), **toupdate)) print('Author deletes 5 random records') for todelete in random.sample(records, 5): client.delete_record(id=todelete['id']) expected = existing + 20 + 20 - 5 # 4. ask again for a signature # 2.1 ask for review (noop on old versions) print('Editor asks for review') data = {"status": "to-review"} editor_client.patch_collection(data=data) # 2.2 check the preview collection (if enabled) if preview_client: print('Check preview collection') preview_records = preview_client.get_records() assert len(preview_records) == expected, '%s != %s records' % (len(preview_records), expected) # Diff size is 20 + 5 if updated records are also all deleted, # or 30 if deletions and updates apply to different records. diff_since_last = preview_client.get_records(_since=preview_timestamp) assert 25 <= len(diff_since_last) <= 30, 'Changes since last signature are not consistent' metadata = preview_client.get_collection()['data'] assert preview_signature != metadata['signature'], 'Preview collection not updated' # 2.3 approve the review print('Reviewer approves and triggers signature') data = {"status": "to-sign"} reviewer_client.patch_collection(data=data) # 5. wait for the result # 6. obtain the destination records and serialize canonically. records = list(dest_client.get_records()) assert len(records) == expected, '%s != %s records' % (len(records), expected) timestamp = dest_client.get_records_timestamp() serialized = canonical_json(records, timestamp) print('Hash is %r' % compute_hash(serialized)) # 7. get back the signed hash signature = dest_client.get_collection()['data']['signature'] with open('pub', 'w') as f: f.write(signature['public_key']) # 8. verify the signature matches the hash signer = ECDSASigner(public_key='pub') try: signer.verify(serialized, signature) print('Signature OK') except Exception: print('Signature KO') raise
class RecordTest(unittest.TestCase): def setUp(self): self.session = mock.MagicMock() self.client = Client( session=self.session, bucket='mybucket', collection='mycollection') def test_record_id_is_given_after_creation(self): mock_response(self.session, data={'id': 5678}) record = self.client.create_record({'foo': 'bar'}) assert 'id' in record['data'].keys() def test_generated_record_id_is_an_uuid(self): mock_response(self.session) self.client.create_record({'foo': 'bar'}) id = self.session.request.mock_calls[0][1][1].split('/')[-1] uuid_regexp = r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}' self.assertRegexpMatches(id, uuid_regexp) def test_records_handles_permissions(self): mock_response(self.session) self.client.create_record( {'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'foo': 'bar', 'id': '1234'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_collection_argument_takes_precedence(self): mock_response(self.session) # Specify a different collection name for the client and the operation. client = Client(session=self.session, bucket='mybucket', collection='wrong_collection') client.update_record(data={'id': '1234'}, collection='good_collection', permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/good_collection/records/1234', data={'id': '1234'}, headers=None, permissions=mock.sentinel.permissions) def test_record_id_is_derived_from_data_if_present(self): mock_response(self.session) self.client.create_record(data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_data_and_permissions_are_added_on_create(self): mock_response(self.session) data = {'foo': 'bar'} permissions = {'read': ['mle']} self.client.create_record( id='1234', data=data, permissions=permissions) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=permissions, headers=DO_NOT_OVERWRITE) def test_creation_sends_if_none_match_by_default(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record( id='1234', data=data) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=DO_NOT_OVERWRITE) def test_creation_doesnt_add_if_none_match_when_overwrite(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data, safe=False) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=None) def test_records_issues_a_request_on_delete(self): mock_response(self.session) self.client.delete_record('1234') url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_issues_a_request_on_retrieval(self): mock_response(self.session, data={'foo': 'bar'}) record = self.client.get_record('1234') self.assertEquals(record['data'], {'foo': 'bar'}) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('get', url) def test_collection_can_retrieve_all_records(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}]) records = self.client.get_records() assert list(records) == [{'id': 'foo'}, {'id': 'bar'}] def test_collection_can_retrieve_records_timestamp(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"12345"'}) timestamp = self.client.get_records_timestamp() assert timestamp == '12345' def test_records_timestamp_is_cached(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"12345"'}) self.client.get_records() timestamp = self.client.get_records_timestamp() assert timestamp == '12345' assert self.session.request.call_count == 1 def test_records_timestamp_is_cached_per_collection(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"12345"'}) self.client.get_records(collection="foo") mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"67890"'}) self.client.get_records(collection="bar") timestamp = self.client.get_records_timestamp("foo") assert timestamp == '12345' timestamp = self.client.get_records_timestamp("bar") assert timestamp == '67890' def test_pagination_is_followed(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] records = self.client.get_records('bucket', 'collection') assert list(records) == [ {'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, {'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ] def test_pagination_supports_if_none_match(self): link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] self.client.get_records('bucket', 'collection', if_none_match="1234") # Check that the If-None-Match header is present in the requests. self.session.request.assert_any_call( 'get', '/buckets/collection/collections/bucket/records', headers={'If-None-Match': '"1234"'}, params={}) self.session.request.assert_any_call( 'get', link, headers={'If-None-Match': '"1234"'}, params={}) def test_collection_can_delete_a_record(self): mock_response(self.session, data={'id': 1234}) resp = self.client.delete_record(id=1234) assert resp == {'id': 1234} url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_delete_if_match(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record( collection='mycollection', bucket='mybucket', id='1', if_match=1234) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers={'If-Match': '"1234"'}) def test_record_delete_if_match_not_included_if_not_safe(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record( collection='mycollection', bucket='mybucket', id='1', if_match=1234, safe=False) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers=None) def test_update_record_gets_the_id_from_data_if_exists(self): mock_response(self.session) self.client.update_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=None, permissions=None) def test_update_record_handles_if_match(self): mock_response(self.session) self.client.update_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}, if_match=1234) headers = {'If-Match': '"1234"'} self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=headers, permissions=None) def test_patch_record_uses_the_patch_method(self): mock_response(self.session) self.client.patch_record( bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=None, permissions=None) def test_update_record_raises_if_no_id_is_given(self): with self.assertRaises(KeyError) as cm: self.client.update_record( data={'foo': 'bar'}, # Omit the id on purpose here. bucket='mybucket', collection='mycollection' ) assert text_type(cm.exception) == ( "'Unable to update a record, need an id.'") def test_get_or_create_doesnt_raise_in_case_of_conflict(self): data = { 'permissions': mock.sentinel.permissions, 'data': {'foo': 'bar'} } self.session.request.side_effect = [ get_http_error(status=412), (data, None) ] returned_data = self.client.create_record( bucket="buck", collection="coll", data={'id': 1234, 'foo': 'bar'}, if_not_exists=True) # Should not raise. assert returned_data == data def test_get_or_create_raise_in_other_cases(self): self.session.request.side_effect = get_http_error(status=500) with self.assertRaises(KintoException): self.client.create_record( bucket="buck", collection="coll", data={'foo': 'bar'}, if_not_exists=True) def test_create_record_raises_a_special_error_on_403(self): self.session.request.side_effect = get_http_error(status=403) with self.assertRaises(KintoException) as e: self.client.create_record( bucket="buck", collection="coll", data={'foo': 'bar'}) expected_msg = ("Unauthorized. Please check that the collection exists" " and that you have the permission to create or write " "on this collection record.") assert e.exception.message == expected_msg
class FunctionalTest(unittest2.TestCase): def __init__(self, *args, **kwargs): super(FunctionalTest, self).__init__(*args, **kwargs) # XXX Read the configuration from env variables. self.server_url = SERVER_URL self.auth = DEFAULT_AUTH # Read the configuration. self.config = configparser.RawConfigParser() self.config.read(os.path.join(__HERE__, 'config/kinto.ini')) self.client = Client(server_url=self.server_url, auth=self.auth) def tearDown(self): # Delete all the created objects flush_url = urljoin(self.server_url, '/__flush__') resp = requests.post(flush_url) resp.raise_for_status() def get_user_id(self, credentials): hmac_secret = self.config.get('app:main', 'kinto.userid_hmac_secret') credentials = '%s:%s' % credentials digest = kinto_core_utils.hmac_digest(hmac_secret, credentials) return 'basicauth:%s' % digest def test_bucket_creation(self): bucket = self.client.create_bucket('mozilla') user_id = self.get_user_id(self.auth) assert user_id in bucket['permissions']['write'] def test_bucket_creation_if_not_exists(self): self.client.create_bucket('mozilla') # Should not raise. self.client.create_bucket('mozilla', if_not_exists=True) def test_buckets_retrieval(self): self.client.create_bucket('mozilla') buckets = self.client.get_buckets() assert len(buckets) == 1 def test_bucket_retrieval(self): self.client.create_bucket('mozilla') self.client.get_bucket('mozilla') # XXX Add permissions handling during creation and check they are # present during retrieval. def test_bucket_modification(self): bucket = self.client.create_bucket('mozilla', data={'version': 1}) assert bucket['data']['version'] == 1 bucket = self.client.patch_bucket('mozilla', data={'author': 'you'}) assert bucket['data']['version'] == 1 assert bucket['data']['author'] == 'you' bucket = self.client.update_bucket('mozilla', data={'date': 'today'}) assert bucket['data']['date'] == 'today' assert 'version' not in bucket['data'] def test_bucket_retrieval_fails_when_not_created(self): self.assertRaises(BucketNotFound, self.client.get_bucket, 'non-existent') def test_bucket_deletion(self): self.client.create_bucket('mozilla') self.client.delete_bucket('mozilla') self.assertRaises(BucketNotFound, self.client.get_bucket, 'mozilla') def test_bucket_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.delete_bucket('mozilla') self.client.delete_bucket('mozilla', if_exists=True) def test_buckets_deletion(self): self.client.create_bucket('mozilla') buckets = self.client.delete_buckets() assert buckets[0]['id'] == 'mozilla' self.assertRaises(BucketNotFound, self.client.get_bucket, 'mozilla') def test_buckets_deletion_when_no_buckets_exist(self): deleted_buckets = self.client.delete_buckets() assert len(deleted_buckets) == 0 def test_bucket_save(self): self.client.create_bucket('mozilla', permissions={'write': ['alexis']}) bucket = self.client.get_bucket('mozilla') assert 'alexis' in bucket['permissions']['write'] def test_group_creation(self): self.client.create_bucket('mozilla') self.client.create_group( 'payments', bucket='mozilla', data={'members': ['blah', ]}, permissions={'write': ['blah', ]}) # Test retrieval of a group gets the permissions as well. group = self.client.get_group('payments', bucket='mozilla') assert 'blah' in group['permissions']['write'] def test_group_creation_if_not_exists(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group( 'payments', bucket='mozilla', data={'members': ['blah', ]}, permissions={'write': ['blah', ]}, if_not_exists=True) def test_group_creation_if_bucket_does_not_exist(self): with pytest.raises(KintoException): self.client.create_group( 'payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group( 'payments', bucket='mozilla', data={'members': ['blah', ]}, if_not_exists=True) def test_group_update(self): self.client.create_bucket('mozilla') group = self.client.create_group( 'payments', bucket='mozilla', data={'members': ['blah', ]}, if_not_exists=True) assert group['data']['members'][0] == 'blah' group = self.client.update_group( data={'members': ['blah', 'foo']}, group='payments', bucket='mozilla') self.assertEquals(group['data']['members'][1], 'foo') def test_group_list(self): self.client.create_bucket('mozilla') self.client.create_group('receipts', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group('assets', bucket='mozilla', data={'members': ['blah', ]}) # The returned groups should be strings. groups = self.client.get_groups('mozilla') self.assertEquals(2, len(groups)) self.assertEquals(set([coll['id'] for coll in groups]), set(['receipts', 'assets'])) def test_group_deletion(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_group('payments', bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_group_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.create_group('payments', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_group('payments', bucket='mozilla') self.client.delete_group('payments', bucket='mozilla', if_exists=True) def test_group_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_group('payments', bucket='mozilla', if_exists=True) def test_groups_deletion(self): self.client.create_bucket('mozilla') self.client.create_group('amo', bucket='mozilla', data={'members': ['blah', ]}) self.client.create_group('blocklist', bucket='mozilla', data={'members': ['blah', ]}) self.client.delete_groups(bucket='mozilla') assert len(self.client.get_groups(bucket='mozilla')) == 0 def test_groups_deletion_when_no_groups_exist(self): self.client.create_bucket('mozilla') deleted_groups = self.client.delete_groups(bucket='mozilla') assert len(deleted_groups) == 0 def test_collection_creation(self): self.client.create_bucket('mozilla') self.client.create_collection( 'payments', bucket='mozilla', permissions={'write': ['alexis', ]} ) # Test retrieval of a collection gets the permissions as well. collection = self.client.get_collection('payments', bucket='mozilla') assert 'alexis' in collection['permissions']['write'] def test_collection_creation_if_not_exists(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') # Should not raise. self.client.create_collection('payments', bucket='mozilla', if_not_exists=True) def test_collection_list(self): self.client.create_bucket('mozilla') self.client.create_collection('receipts', bucket='mozilla') self.client.create_collection('assets', bucket='mozilla') # The returned collections should be strings. collections = self.client.get_collections('mozilla') self.assertEquals(2, len(collections)) self.assertEquals(set([coll['id'] for coll in collections]), set(['receipts', 'assets'])) def test_collection_deletion(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collection_deletion_if_exists(self): self.client.create_bucket('mozilla') self.client.create_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla') self.client.delete_collection('payments', bucket='mozilla', if_exists=True) def test_collection_deletion_can_still_raise_errors(self): error = KintoException("An error occured") with mock.patch.object(self.client.session, 'request', side_effect=error): with pytest.raises(KintoException): self.client.delete_collection('payments', bucket='mozilla', if_exists=True) def test_collections_deletion(self): self.client.create_bucket('mozilla') self.client.create_collection('amo', bucket='mozilla') self.client.create_collection('blocklist', bucket='mozilla') self.client.delete_collections(bucket='mozilla') assert len(self.client.get_collections(bucket='mozilla')) == 0 def test_collections_deletion_when_no_collections_exist(self): self.client.create_bucket('mozilla') deleted_collections = self.client.delete_collections(bucket='mozilla') assert len(deleted_collections) == 0 def test_record_creation_and_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) record = client.get_record(created['data']['id']) assert 'alexis' in record['permissions']['read'] def test_records_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) records = client.get_records() assert len(records) == 1 def test_records_paginated_list_retrieval(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() for i in range(10): client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) # Kinto is running with kinto.paginate_by = 5 records = client.get_records() assert len(records) == 10 def test_single_record_save(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) created['data']['bar'] = 'baz' # XXX enhance this in order to have to pass only one argument, created. client.update_record(id=created['data']['id'], data=created['data']) retrieved = client.get_record(created['data']['id']) assert 'alexis' in retrieved['permissions']['read'] assert retrieved['data']['foo'] == u'bar' assert retrieved['data']['bar'] == u'baz' assert created['data']['id'] == retrieved['data']['id'] def test_single_record_doesnt_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) with self.assertRaises(KintoException): # Create a second record with the ID of the first one. client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}) def test_single_record_creation_if_not_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}) client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}, if_not_exists=True) def test_single_record_can_overwrite(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() created = client.create_record(data={'foo': 'bar'}, permissions={'read': ['alexis']}) client.create_record(data={'id': created['data']['id'], 'bar': 'baz'}, safe=False) def test_one_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) assert deleted['deleted'] is True assert len(client.get_records()) == 0 def test_record_deletion_if_exists(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() record = client.create_record({'foo': 'bar'}) deleted = client.delete_record(record['data']['id']) deleted_if_exists = client.delete_record(record['data']['id'], if_exists=True) assert deleted['deleted'] is True assert deleted_if_exists is None def test_multiple_record_deletion(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.create_record({'foo': 'bar'}) client.delete_records() assert len(client.get_records()) == 0 def test_records_deletion_when_no_records_exist(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() deleted_records = client.delete_records() assert len(deleted_records) == 0 def test_bucket_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a bucket and share it with alice. self.client.create_bucket('shared-bucket', permissions={'read': [alice_userid, ]}) alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_bucket('shared-bucket') def test_updating_data_on_a_group(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla') client.create_bucket() client.create_group('payments', data={'members': []}) client.patch_group('payments', data={'secret': 'psssssst!'}) group = client.get_group('payments') assert group['data']['secret'] == 'psssssst!' def test_updating_data_on_a_collection(self): client = Client(server_url=self.server_url, auth=self.auth, bucket='mozilla', collection='payments') client.create_bucket() client.create_collection() client.patch_collection(data={'secret': 'psssssst!'}) collection = client.get_collection() assert collection['data']['secret'] == 'psssssst!' def test_collection_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) self.client.create_bucket('bob-bucket') self.client.create_collection( 'shared', bucket='bob-bucket', permissions={'read': [alice_userid, ]}) # Try to read the collection as Alice. alice_client = Client(server_url=self.server_url, auth=alice_credentials) alice_client.get_collection('shared', bucket='bob-bucket') def test_record_sharing(self): alice_credentials = ('alice', 'p4ssw0rd') alice_userid = self.get_user_id(alice_credentials) # Create a record, and share it with Alice. self.client.create_bucket('bob-bucket') self.client.create_collection('bob-personal-collection', bucket='bob-bucket') record = self.client.create_record( data={'foo': 'bar'}, permissions={'read': [alice_userid, ]}, bucket='bob-bucket', collection='bob-personal-collection') # Try to read the record as Alice alice_client = Client(server_url=self.server_url, auth=alice_credentials) record = alice_client.get_record( id=record['data']['id'], bucket='bob-bucket', collection='bob-personal-collection') assert record['data']['foo'] == 'bar' def test_request_batching(self): with self.client.batch(bucket='mozilla', collection='fonts') as batch: batch.create_bucket() batch.create_collection() batch.create_record(data={'foo': 'bar'}, permissions={'read': ['natim']}) batch.create_record(data={'bar': 'baz'}, permissions={'read': ['alexis']}) records = self.client.get_records(bucket='mozilla', collection='fonts') assert len(records) == 2 def test_replication(self): # First, create a few records on the first kinto collection. with self.client.batch(bucket='origin', collection='coll') as batch: batch.create_bucket() batch.create_collection() for n in range(10): batch.create_record(data={'foo': 'bar', 'n': n}) origin = Client( server_url=self.server_url, auth=self.auth, bucket='origin', collection='coll' ) destination = Client( server_url=self.server_url, auth=self.auth, bucket='destination', collection='coll') replication.replicate(origin, destination) records = self.client.get_records(bucket='destination', collection='coll') assert len(records) == 10
from kinto_http import Client source = Client(server_url="https://kinto.notmyidea.org/v1") destination = Client(server_url="https://kinto.notmyidea.org/v1", auth=('user', 'pass'), bucket='webnotesapp') records = source.get_records(bucket='ametaireau', collection='notes') print("got %s records" % len(records)) destination.create_collection(id='ametaireau', permissions={'read': ['system.Everyone']}) for record in records: destination.create_record(data=record, collection='ametaireau')
class AppWindow(QtWidgets.QMainWindow,Ui_MainWindow): def __init__(self, parent = None): QtWidgets.QMainWindow.__init__(self, parent) self.setAttribute(QtCore.Qt.WA_DeleteOnClose) self.setupUi(self) ###################################################################################### ###################################################################################### self.settings = Settings() self.initRep() self.initKintoClient() self.syncRecipes() self.dlgEditG = Dialog(self) self.dlgEditH = DialogH(self) self.dlgEditD = DialogD(self) self.dlgEditY = DialogL(self) self.dlgPref = DialogPref(self) self.dlgStep = DialogStep(self) self.dlgMash = DialogMash(self) self.base = ImportBase() self.mashProfileExport = ExportMash() # self.base.importBeerXML() self.s=0 self.recipe = None #Les connexions self.actionEnregistrer.triggered.connect(self.enregistrer) self.actionQuitter.triggered.connect(app.quit) # self.connect(self.actionQuitter, QtCore.SIGNAL("triggered()"), app, QtCore.SLOT("quit()")) self.actionShowJournal.triggered.connect(self.showJournal) self.actionEditGrains.triggered.connect(self.editGrains) self.actionEditHoublons.triggered.connect(self.editHoublons) self.actionEditDivers.triggered.connect(self.editDivers) self.actionEditLevures.triggered.connect(self.editLevures) self.actionRestaurerIngredients.triggered.connect(self.restoreDataBase) self.actionImportIng.triggered.connect(self.importIng) self.actionManageProfiles.triggered.connect(self.seeMash) self.actionAbout.triggered.connect(self.about) self.actionAllTools.triggered.connect(self.showTools) self.actionPreferences.triggered.connect(self.dialogPreferences) ####################################################################################################### # Profil de brassage ######################################################################################################### self.listWidgetSteps.itemSelectionChanged.connect (self.stepDetails) self.listWidgetMashProfiles.itemSelectionChanged.connect (self.mashClicked) self.buttonBoxMashDetails.rejected.connect(self.mashRejected) # self.comboBoxStepType.addItems(["Infusion", "Température", "Décoction"]) self.pushButtonStepEdit.clicked.connect(self.stepEdit) self.dlgStep.stepChanged.connect(self.stepReload) self.pushButtonStepRemove.clicked.connect(self.removeStep) self.pushButtonNewStep.clicked.connect(self.addStep) self.pushButtonMashEdit.clicked.connect(self.mashEdit) self.dlgMash.mashChanged.connect(self.mashReload) self.pushButtonNewProfile.clicked.connect(self.addMash) self.pushButtonRemoveProfile.clicked.connect(self.removeMash) self.pushButtonSaveProfile.clicked.connect(self.saveProfile) #La bibliotheque ################################################################################################################### ################################################################################################################### # self.listdir(recettes_dir) self.showLib() ################################################################################################### ######## gestion des arguments au lancement du programme ######################################### argumentsList=QtWidgets.QApplication.arguments() if len(argumentsList) > 1 : logger.debug("la liste d'arguments: %s",argumentsList) logger.debug("le chemin: %s",argumentsList[1]) # for part in argumentsList : # recipePath=recipePath + " " + part try: recipePath= argumentsList[1] for part in argumentsList[2:] : recipePath= recipePath +" " + part self.openRecipeFile(recipePath) except : pass else: pass ######################################################################################################################## #################################################################################################################### # le signal émit à la fermeture de la fenêtre de préférences self.dlgPref.prefAccepted.connect(self.prefReload) ########################################################### ############### Journal ############################## ###################################################### def loadJournal(self): self.journal=Journal() self.journal.loadJournal() # self.actionEditJournal.setEnabled(True) @QtCore.pyqtSlot() def showJournal(self,entry=" '' ") : self.stackedWidget.setCurrentIndex(0) self.loadJournal() pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBiblio.setHtml(self.journal.export("html",entry), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject("main", self) # self.webViewBiblio.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) # self.webInspector = QtWebKit.QWebInspector(self) # self.webInspector.setPage(self.webViewBiblio.page()) # self.webInspector.setVisible(True) # self.verticalLayout_13.addWidget(self.webInspector) @QtCore.pyqtSlot(str, str) def addToJournal(self,event, recipeName) : self.loadJournal() entry = '''{recipe:%s,date:%s,event:%s,editing:'True'} ''' %( "'" + recipeName + "'", "'" + str(int(time.time())) + "'" , "'" + self.journal.eventsLabels[event] + "'") self.showJournal(entry) @QtCore.pyqtSlot(str) def dumpJournal(self,journalJson) : journalJson= '{"name":"journal","items": %s }' %journalJson d=json.loads(journalJson) with open(journal_file, mode="w", encoding="utf-8") as f : json.dump(d,f,indent=2) ############## Bibliothèque ############################## ########################################################## @QtCore.pyqtSlot() def showLib(self) : # data = json.dumps(self.recipesSummary) # data = data.replace("'","'") self.stackedWidget.setCurrentIndex(0) self.brewdayLock = 0 self.webSettings = self.webViewBiblio.settings() self.webSettings.setAttribute(QtWebKit.QWebSettings.LocalContentCanAccessRemoteUrls, True) pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/html/")) self.webViewBiblio.setHtml(LibExporterRepository['html'](), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject("main", self) self.webViewBiblio.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) self.webViewBiblio.page().action(QtWebKitWidgets.QWebPage.Reload).setVisible(False) @QtCore.pyqtSlot(str) def deleteLib(self,path) : confirmation = QtWidgets.QMessageBox.question(self, self.tr("Supprimer"), self.tr("La recette sera définitivement supprimée <br/> Continuer ?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if (confirmation == QtWidgets.QMessageBox.Yes): os.remove(path) self.listdir(recettes_dir) self.showLib() else : self.showLib() @QtCore.pyqtSlot() def backWebViewBiblio(self) : self.stackedWidget.setCurrentIndex(0) @QtCore.pyqtSlot(str, str) def saveRecipe(self, recipe, path) : logger.debug(path) recipeFile = QtCore.QFile(path) if recipeFile.open(QtCore.QIODevice.WriteOnly): try: stream = QtCore.QTextStream(recipeFile) stream.setCodec("UTF-8") stream << recipe finally: recipeFile.close() else: # TODO : Prévenir l'utilisateur en cas d'échec de l'enregistrement pass @QtCore.pyqtSlot(result=str) def createPath(self, file_id=None) : if file_id is None: file_id = str(int(time.time()*10)) path = recettes_dir + "/" + file_id + ".xml" logger.debug(path) return path @QtCore.pyqtSlot() def resetLock(self): self.brewdayLock = 0; ############# Mode Brassage ################################ ############################################################ @QtCore.pyqtSlot(str) def showBrewdayMode(self, data): if self.brewdayLock == 0 : self.stackedWidget.setCurrentIndex(1) self.brewdayLock = 1 data = data.replace("'","'") pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBrewday.setHtml(BrewdayExporterRepository['html'](data), baseUrl) self.webViewBrewday.page().mainFrame().addToJavaScriptWindowObject("main", self) self.webViewBrewday.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) self.webViewBrewday.page().action(QtWebKitWidgets.QWebPage.Reload).setVisible(False) else : self.stackedWidget.setCurrentIndex(1) ###### Outils ############################################ ########################################################## @QtCore.pyqtSlot() def showTools(self): self.stackedWidget.setCurrentIndex(0) pyDir = os.path.abspath(os.path.dirname(__file__)) baseUrl = QtCore.QUrl.fromLocalFile(os.path.join(pyDir, "static/")) self.webViewBiblio.setHtml(ToolExporterRepository["html"](), baseUrl) self.webViewBiblio.page().mainFrame().addToJavaScriptWindowObject("main", self) # self.webViewBiblio.page().settings().setAttribute(QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) # self.webInspector = QtWebKit.QWebInspector(self) # self.webInspector.setPage(self.webViewBiblio.page()) # self.webInspector.setVisible(True) # self.verticalLayout_13.addWidget(self.webInspector) @QtCore.pyqtSlot(result=str) def dataRecipes(self) : # f = open(recipeData_file, 'w') # f.write(self.recipesSummary) self.listdir(recettes_dir) return self.recipesSummary @QtCore.pyqtSlot(result=str) def dataProfiles(self) : return self.mashProfileExport.exportJson(ImportBase().listeMashes) @QtCore.pyqtSlot(result=str) def dataIngredients(self) : return ImportBase().exportjson() @QtCore.pyqtSlot(result=str) def dataPref(self) : dic = {} dic["boilOffRate"] = settings.conf.value("BoilOffRate") dic["coolingLossRate"] = settings.conf.value("CoolingLoss") dic["grainTemp"] = settings.conf.value("GrainTemp") dic["fudgeFactor"] = settings.conf.value("FudgeFactor") dic["grainRetention"] = settings.conf.value("GrainRetention") dic = json.dumps(dic) return dic #Une fonction qui gère l'aperçu des couleurs. #Contient un tupple avec plusieurs références de couleurs, classées par rang selon la valeur SRM. ################################################################################################# # def colorPreview (self) : # self.colorTuppleSrm = ('FFE699', 'FFD878', 'FFCA5A', 'FFBF42', 'FBB123', 'F8A600', 'F39C00', 'EA8F00', 'E58500', 'DE7C00', 'D77200', 'CF6900', 'CB6200', 'C35900','BB5100', 'B54C00', 'B04500', 'A63E00', 'A13700', '9B3200', '952D00', '8E2900', '882300', '821E00', '7B1A00', '771900', '701400', '6A0E00', '660D00','5E0B00','5A0A02','600903', '520907', '4C0505', '470606', '440607', '3F0708', '3B0607', '3A070B', '36080A') # colorRef= round(self.recipe.compute_EBC()/1.97) # if colorRef >= 30 : # color = "#" + self.colorTuppleSrm[30] # elif colorRef <= 1 : # color = "#" + self.colorTuppleSrm[0] # else : # color = "#" + self.colorTuppleSrm[colorRef-1] # self.widgetColor.setStyleSheet("background-color :" + color) def liste_fichiers_recettes(self, rootdir): for root, subFolders, files in os.walk(rootdir): for file2 in files: yield (file2, os.path.join(root,file2)) def listdir(self, rootdir) : summaries=[] for filename, recipe in self.liste_fichiers_recettes(rootdir): try : summaries.append(self.jsonRecipeLib(recipe)) except : logger.debug("le fichier %s n'est pas une recette" %(recipe)) self.recipesSummary = "[" + ",".join(summaries) + "]" logger.debug("%s recettes détectées" %(len(summaries))) def jsonRecipeLib(self,recipe) : self.s = recipe self.recipe = Recipe.parse(recipe) data = self.recipe.export("json") data = data[1:-1] return data def initRep(self) : home = QtCore.QDir(home_dir) config = QtCore.QDir(config_dir) logger.debug (config) if not config.exists() : home.mkpath (config_dir) else : pass database = QtCore.QFile(database_file) if not database.exists() : database.copy(database_root, database_file) else : pass recettes = QtCore.QFile(recettes_dir) if not recettes.exists() : try : shutil.copytree(samples_dir, samples_target) except : home.mkpath(recettes_dir) mash = QtCore.QFile(mash_file) if not mash.exists() : mash.copy(mash_root, mash_file) else : pass journal = QtCore.QFile(journal_file) if not journal.exists() : journal.copy(journal_root, journal_file) else : pass # on configure des valeurs par défaut if not settings.conf.contains("BoilOffRate") : settings.conf.setValue("BoilOffRate", 10) if not settings.conf.contains("CoolingLoss") : settings.conf.setValue("CoolingLoss", 5) if not settings.conf.contains("GrainTemp") : settings.conf.setValue("GrainTemp", 20) if not settings.conf.contains("FudgeFactor") : settings.conf.setValue("FudgeFactor", 1.7) if not settings.conf.contains("GrainRetention") : settings.conf.setValue("GrainRetention", 1) if not settings.conf.contains("Menus") : settings.conf.setValue("Menus", "button") def prefReload(self) : if platform == 'win32': recettes_dir = settings.conf.value("pathWin32") else : recettes_dir = settings.conf.value("pathUnix") self.initRep() self.initKintoClient() self.listdir(recettes_dir) self.showLib() def initKintoClient(self): self.kinto_client = None kinto_url = settings.conf.value("KintoServerUrl") if kinto_url is not None and kinto_url != "": kinto_bucket = settings.conf.value("KintoDefaultBucket") kinto_credentials=(settings.conf.value("KintoBasicUserCred"), settings.conf.value("KintoBasicPasswordCred")) logger.debug(kinto_credentials) if kinto_bucket is None or kinto_bucket == "": kinto_bucket = "joliebulle" logger.debug("using default bucket 'joliebulle'") try: tmp_client = Client(server_url=kinto_url, auth = kinto_credentials) tmp_client.create_bucket(id=kinto_bucket, if_not_exists=True) self.kinto_client = Client(server_url=kinto_url, bucket=kinto_bucket, auth = kinto_credentials) # Création des collections self.kinto_client.create_collection(id='recipes', if_not_exists=True) self.kinto_client.create_collection(id='ingredients', if_not_exists=True) logger.info("Synchronize with kinto server at :" + repr(self.kinto_client)) except Exception as e: logger.warn("Failed to initialize Kinto synchronisation: " + repr(e)) def syncRecipes(self): recipes_collection = 'recipes' if self.kinto_client is None: return # Sync local recipes id_recettes_locales=[] for filename, full_filename in self.liste_fichiers_recettes(recettes_dir): recipe_id = filename.replace('.xml', '') id_recettes_locales.append(recipe_id) try: remote_recipe = self.kinto_client.get_record(id=recipe_id, collection=recipes_collection) logger.debug("recipe " + recipe_id + " already exists on server, update if needed") remote_timestamp = int(remote_recipe['data']['last_modified']/1000) local_timestamp = int(os.path.getmtime(full_filename)) logger.debug(str(remote_timestamp) + " " + str(local_timestamp)) if remote_timestamp < local_timestamp: logger.info("Mise à jour de la recette distante " + recipe_id) # La recette distant doit être mise à jour local_recipe = Recipe.parse(full_filename) data = local_recipe.export("dict") ret = self.kinto_client.update_record(id=recipe_id, collection=recipes_collection, data=data) new_timestamp = ret['data']['last_modified'] #MAJ du mtime du fichier pour marquer la synchronisation os.utime(full_filename, times=(int(new_timestamp/1000), int(new_timestamp/1000))) if remote_timestamp > local_timestamp: # La recette distante doit être mise à jour logger.info("Mise à jour de la recette locale " + recipe_id) new_recipe = Recipe.parse(remote_recipe['data'], "dict") self.doEnregistrerRecette(new_recipe, full_filename) os.utime(full_filename, times=(int(remote_timestamp), int(remote_timestamp))) except KintoException: logger.debug("recipe " + recipe_id + " doesn't exists on server, sending it") new_recipe = Recipe.parse(full_filename) data = new_recipe.export("dict") ret = self.kinto_client.create_record(id=recipe_id, collection=recipes_collection, data=data) new_timestamp = ret['data']['last_modified'] #MAJ du mtime du fichier pour marquer la synchronisation os.utime(full_filename, times=(int(new_timestamp/1000), int(new_timestamp/1000))) recipes = self.kinto_client.get_records(collection='recipes') for recipe in recipes: if recipe['id'] not in id_recettes_locales: #Recette distante non présente localement new_recipe = Recipe.parse(recipe, "dict") fullname = recipe['id'] + ".xml" logger.debug("Création de la recette " + fullname) self.doEnregistrerRecette(new_recipe, os.path.join(recettes_dir, fullname)) os.utime(full_filename, times=(int(remote_timestamp), int(remote_timestamp))) @QtCore.pyqtSlot() def switchToLibrary(self) : self.stackedWidget.setCurrentIndex(0) # self.viewRecipeLib(self.s) def switchToMash(self) : self.stackedWidget.setCurrentIndex(2) def restoreDataBase(self) : home = QtCore.QDir(home_dir) config = QtCore.QDir(config_dir) database = QtCore.QFile(database_file) confirmation = QtWidgets.QMessageBox.question(self, self.tr("Remplacer la base ?"), self.tr("La base des ingrédients actuelle va être effacée et remplacée par la base originale. Toutes vos modifications vont être effacées. Un redémarrage de l'application sera nécessaire.<br> Continuer ?"), QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No) if (confirmation == QtWidgets.QMessageBox.Yes): database.remove(database_file) database.copy(database_root, database_file) else : pass def editGrains(self) : self.dlgEditG.setModal(True) self.dlgEditG.setModel() self.dlgEditG.show() def editHoublons(self) : self.dlgEditH.setModal(True) self.dlgEditH.setModel() self.dlgEditH.show() def editDivers(self) : self.dlgEditD.setModal(True) self.dlgEditD.setModel() self.dlgEditD.show() def editLevures(self) : self.dlgEditY.setModal(True) self.dlgEditY.setModel() self.dlgEditY.show() @QtCore.pyqtSlot(float, float, float, float) def preBoilCheck(self,volPreBoil,preBoilSg,GU,volume) : self.dlgPreBoil = DialogPreBoil(self) self.dlgPreBoil.setData(volPreBoil,preBoilSg,GU,volume) self.dlgPreBoil.setModal(True) self.dlgPreBoil.show() def dialogPreferences (self) : self.dlgPref.setModal(True) self.dlgPref.show() def importBeerXML(self) : fichierBeerXML = self.s try: self.recipe = Recipe.parse(fichierBeerXML) self.currentRecipeMash = self.recipe.mash except : errors = Errors() errors.warningXml() def about(self) : about = DialogAbout(self) about.show() def doEnregistrerRecette(self, recipe, destination): recipeFile = QtCore.QFile(destination) if recipeFile.open(QtCore.QIODevice.WriteOnly): try: stream = QtCore.QTextStream(recipeFile) stream.setCodec("UTF-8") stream << recipe.export("beerxml") finally: recipeFile.close() else: # TODO : Prévenir l'utilisateur en cas d'échec de l'enregistrement pass def enregistrerRecette(self, destination): self.doEnregistrerRecette(self.recipe, destination) self.fileSaved = True def enregistrer (self) : if self.recipe.name != self.lineEditRecette.text() : self.nameChanged = True else : self.nameChanged = False self.recipe.name = self.lineEditRecette.text() self.recipe.style = self.lineEditGenre.text() self.recipe.brewer = self.lineEditBrewer.text() self.recipe.boil = self.spinBoxBoil.value() if not self.s: destination = recettes_dir + "/" + self.recipe.name.replace('/', ' ') + ".xml" if os.path.exists(destination) : errors=Errors() errors.warningExistingPath() self.fileSaved = False else : self.s = destination self.enregistrerRecette(destination) else : self.enregistrerRecette(self.s) def enregistrerSous (self) : self.s = QtGui.QFileDialog.getSaveFileName (self, self.tr("Enregistrer dans un fichier"), recettes_dir + "/" + self.recipe.name.replace('/', ' ') + ".xml", "BeerXML (*.xml)") self.enregistrerRecette(self.s) @QtCore.pyqtSlot(str) def copyBbcode (self, bbcode): app.clipboard().setText(bbcode) def importIng(self): s = QtWidgets.QFileDialog.getOpenFileName(self, self.tr("Ouvrir un fichier"), home_dir, ) if not s : pass else : self.importIngList = ImportIng() self.importIngList.parseFile(s) def mashComboChanged (self) : #on remet le verrou à 0, il va falloir recalculer en repassant en brewday mode self.brewdayLock = 0 try : i =self.comboBoxMashProfiles.currentIndex() self.currentMash = ImportBase().listeMashes[i] except : self.currentMash = self.currentRecipeMash if i == -1 : self.currentMash = Mash() self.recipe.mash = self.currentMash def seeMash(self) : self.switchToMash() index = self.listWidgetMashProfiles.currentRow() i = self.listWidgetSteps.currentRow() self.listWidgetMashProfiles.clear() self.listWidgetSteps.clear() self.numMash = len(ImportBase().listeMashes) #self.numSteps = self.mashProfilesBase.numSteps self.popMashList() self.pushButtonMashEdit.setEnabled(False) self.pushButtonRemoveProfile.setEnabled(False) self.pushButtonStepRemove.setEnabled(False) self.pushButtonStepEdit.setEnabled(False) self.listWidgetMashProfiles.setCurrentRow(index) self.listWidgetSteps.setCurrentRow(i) def popMashList(self) : self.listWidgetMashProfiles.clear() for mash in ImportBase().listeMashes : self.listWidgetMashProfiles.addItem(mash.name) def mashClicked(self) : self.listWidgetSteps.clear() index = self.listWidgetMashProfiles.currentRow() if index > -1: mash = ImportBase().listeMashes[index] for step in mash.listeSteps : self.listWidgetSteps.addItem(step.name) self.labelStepName.setTextFormat(QtCore.Qt.RichText) self.labelMashName.setText("<b>" + mash.name + "</b>") self.labelMashPh.setText("%.1f" %float(mash.ph)) # self.labelMashGrainTemp.setText("%.1f" %float(self.dicMashDetail['grainTemp'])) # self.labelMashTunTemp.setText("%.1f" %float(self.dicMashDetail['tunTemp'])) try : self.labelMashSpargeTemp.setText("%.1f" %float(mash.spargeTemp)) except : pass try : self.listWidgetSteps.setCurrentRow(0) except : pass # print(self.dicMashDetail) self.pushButtonMashEdit.setEnabled(True) self.pushButtonRemoveProfile.setEnabled(True) def mashDetails(self) : self.dlgMashDetail = DialogMashDetail(self) self.dlgMashDetail.setModal(True) self.dlgMashDetail.show() self.dlgMashDetail.setFields(self.currentMash) self.dlgMashDetail.setAttribute( QtCore.Qt.WA_DeleteOnClose, True ) def stepDetails(self) : index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: try: selected_step = selected_mash.listeSteps[i] self.labelStepName.setTextFormat(QtCore.Qt.RichText) self.labelStepName.setText("<b>" + selected_step.name +"</b>") self.labelStepType.setText(selected_step.type) self.labelStepTemp.setText(MashStepView.temp_to_display(selected_step.temp)) self.labelStepTime.setText(MashStepView.time_to_display(selected_step.time)) self.pushButtonStepRemove.setEnabled(True) self.pushButtonStepEdit.setEnabled(True) except: pass def stepEdit(self) : index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: selected_step = selected_mash.listeSteps[i] self.dlgStep.show() self.dlgStep.fields (selected_step) def stepReload(self, step) : index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: selected_step = selected_mash.listeSteps[i] selected_step.name = step.name selected_step.type = step.type selected_step.temp = step.temp selected_step.time = step.time self.seeMash() self.stepDetails() self.listWidgetMashProfiles.setCurrentRow(index) self.listWidgetSteps.setCurrentRow(i) def removeStep(self) : index = self.listWidgetMashProfiles.currentRow() if index > -1: selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() if i > -1: item = self.listWidgetSteps.currentItem() del selected_mash.listeSteps[i] # self.listWidgetSteps.clearSelection() #self.listWidgetSteps.takeItem(item) #On force la sélection sur la ligne précédente self.listWidgetSteps.setCurrentRow(i-1) self.seeMash() def addStep(self) : index = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[index] i = self.listWidgetSteps.currentRow() step = MashStep() step.name = 'Nouveau palier' step.type = 'Infusion' step.time = '0' step.temp = '0' step.vol = '0' selected_mash.listeSteps.append(step) self.listWidgetMashProfiles.setCurrentRow(index) self.seeMash() self.stepDetails() self.listWidgetMashProfiles.setCurrentRow(index) # self.listWidgetSteps.setCurrentRow(i-1) # self.stepEdit() def mashEdit(self) : index = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[index] self.dlgMash.show() self.dlgMash.fields(selected_mash) def mashReload(self,mash) : #on remet le verrou à 0, il va falloir recalculer en repassant en brewday mode self.brewdayLock = 0 f = self.listWidgetMashProfiles.currentRow() selected_mash = ImportBase().listeMashes[f] selected_mash.name = mash.name selected_mash.ph = mash.ph selected_mash.grainTemp = 20 selected_mash.tunTemp = 20 selected_mash.spargeTemp = mash.spargeTemp self.popMashList() self.listWidgetMashProfiles.setCurrentRow(f) def addMash(self) : new_mash = Mash() new_mash.name = 'Nouveau profil' new_mash.grainTemp = '0' new_mash.tunTemp = '0' new_mash.spargeTemp = '78' new_mash.ph = 5.4 new_step = MashStep() new_step.name = 'Nouveau Palier' new_step.type = 'Infusion' new_step.time = '0' new_step.temp = '0' new_mash.listeSteps.append(new_step) ImportBase().listeMashes.append(new_mash) self.seeMash() self.listWidgetMashProfiles.setCurrentRow(len(ImportBase().listeMashes)-1) def removeMash(self) : i = self.listWidgetMashProfiles.currentRow() del ImportBase().listeMashes[i] self.seeMash() self.listWidgetSteps.clear() def mashRejected (self) : self.showLib() def saveProfile(self) : self.mashProfileExport.export(ImportBase().listeMashes) self.mashProfileExport.enregistrer(mash_file) @QtCore.pyqtSlot() def printRecipe (self) : printer=QtPrintSupport.QPrinter() dialog = QtPrintSupport.QPrintDialog(printer) dialog.setModal(True) dialog.setWindowTitle("Print Document" ) if dialog.exec_() == True: self.webViewBiblio.print(printer) # document=QtGui.QTextDocument() # stringHtml=self.recipe.export("print") # document.setHtml(stringHtml) # document.print(printer) @QtCore.pyqtSlot() def printBrewday(self) : printer=QtPrintSupport.QPrinter() dialog = QtPrintSupport.QPrintDialog(printer) dialog.setModal(True) dialog.setWindowTitle("Print Document" ) if dialog.exec_() == True: self.webViewBrewday.print(printer)
def backport_records(event, context, **kwargs): """Backport records creations, updates and deletions from one collection to another. """ server_url = event["server"] source_auth = ( event.get("backport_records_source_auth") or os.environ["BACKPORT_RECORDS_SOURCE_AUTH"] ) source_bucket = ( event.get("backport_records_source_bucket") or os.environ["BACKPORT_RECORDS_SOURCE_BUCKET"] ) source_collection = ( event.get("backport_records_source_collection") or os.environ["BACKPORT_RECORDS_SOURCE_COLLECTION"] ) dest_auth = event.get( "backport_records_dest_auth", os.getenv("BACKPORT_RECORDS_DEST_AUTH", source_auth), ) dest_bucket = event.get( "backport_records_dest_bucket", os.getenv("BACKPORT_RECORDS_DEST_BUCKET", source_bucket), ) dest_collection = event.get( "backport_records_dest_collection", os.getenv("BACKPORT_RECORDS_DEST_COLLECTION", source_collection), ) if source_bucket == dest_bucket and source_collection == dest_collection: raise ValueError("Cannot copy records: destination is identical to source") source_client = Client( server_url=server_url, bucket=source_bucket, collection=source_collection, auth=tuple(source_auth.split(":", 1)) if ":" in source_auth else BearerTokenAuth(source_auth), ) dest_client = Client( server_url=server_url, bucket=dest_bucket, collection=dest_collection, auth=tuple(dest_auth.split(":", 1)) if ":" in dest_auth else BearerTokenAuth(dest_auth), ) source_timestamp = source_client.get_records_timestamp() dest_timestamp = dest_client.get_records_timestamp() if source_timestamp <= dest_timestamp: print("Records are in sync. Nothing to do.") return source_records = source_client.get_records() dest_records_by_id = {r["id"]: r for r in dest_client.get_records()} with dest_client.batch() as dest_batch: # Create or update the destination records. for r in source_records: dest_record = dest_records_by_id.pop(r["id"], None) if dest_record is None: dest_batch.create_record(data=r) elif r["last_modified"] > dest_record["last_modified"]: dest_batch.update_record(data=r) # Delete the records missing from source. for r in dest_records_by_id.values(): dest_batch.delete_record(id=r["id"]) ops_count = len(dest_batch.results()) # If destination has signing, request review or auto-approve changes. server_info = dest_client.server_info() signer_config = server_info["capabilities"].get("signer", {}) signer_resources = signer_config.get("resources", []) # Check destination collection config (sign-off required etc.) signed_dest = [ r for r in signer_resources if r["source"]["bucket"] == dest_bucket and r["source"]["collection"] == dest_collection ] if len(signed_dest) == 0: # Not explicitly configured. Check if configured at bucket level? signed_dest = [ r for r in signer_resources if r["source"]["bucket"] == dest_bucket and r["source"]["collection"] is None ] # Destination has no signature enabled. Nothing to do. if len(signed_dest) == 0: print(f"Done. {ops_count} changes applied.") return has_autoapproval = not signed_dest[0].get( "to_review_enabled", signer_config["to_review_enabled"] ) and not signed_dest[0].get( "group_check_enabled", signer_config["group_check_enabled"] ) if has_autoapproval: # Approve the changes. dest_client.patch_collection(data={"status": "to-sign"}) print(f"Done. {ops_count} changes applied and signed.") else: # Request review. dest_client.patch_collection(data={"status": "to-review"}) print(f"Done. Requested review for {ops_count} changes.")
class RecordTest(unittest.TestCase): def setUp(self): self.session = mock.MagicMock() self.session.request.return_value = (mock.sentinel.response, mock.sentinel.count) self.client = Client( session=self.session, bucket='mybucket', collection='mycollection') def test_record_id_is_given_after_creation(self): mock_response(self.session, data={'id': 5678}) record = self.client.create_record(data={'foo': 'bar'}) assert 'id' in record['data'].keys() def test_generated_record_id_is_an_uuid(self): mock_response(self.session) self.client.create_record(data={'foo': 'bar'}) id = self.session.request.mock_calls[0][1][1].split('/')[-1] uuid_regexp = r'[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}' self.assertRegex(id, uuid_regexp) def test_records_handles_permissions(self): mock_response(self.session) self.client.create_record(data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'foo': 'bar', 'id': '1234'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_collection_argument_takes_precedence(self): mock_response(self.session) # Specify a different collection name for the client and the operation. client = Client(session=self.session, bucket='mybucket', collection='wrong_collection') client.update_record(data={'id': '1234'}, collection='good_collection', permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/good_collection/records/1234', data={'id': '1234'}, headers=None, permissions=mock.sentinel.permissions) def test_record_id_is_derived_from_data_if_present(self): mock_response(self.session) self.client.create_record(data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1234', data={'id': '1234', 'foo': 'bar'}, permissions=mock.sentinel.permissions, headers=DO_NOT_OVERWRITE) def test_data_and_permissions_are_added_on_create(self): mock_response(self.session) data = {'foo': 'bar'} permissions = {'read': ['mle']} self.client.create_record(id='1234', data=data, permissions=permissions) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=permissions, headers=DO_NOT_OVERWRITE) def test_creation_sends_if_none_match_by_default(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=DO_NOT_OVERWRITE) def test_creation_doesnt_add_if_none_match_when_overwrite(self): mock_response(self.session) data = {'foo': 'bar'} self.client.create_record(id='1234', data=data, safe=False) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with( 'put', url, data=data, permissions=None, headers=None) def test_records_issues_a_request_on_delete(self): mock_response(self.session) self.client.delete_record(id='1234') url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_issues_a_request_on_retrieval(self): mock_response(self.session, data={'foo': 'bar'}) record = self.client.get_record(id='1234') self.assertEqual(record['data'], {'foo': 'bar'}) url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('get', url) def test_collection_can_retrieve_all_records(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}]) records = self.client.get_records() assert list(records) == [{'id': 'foo'}, {'id': 'bar'}] def test_collection_can_retrieve_records_timestamp(self): mock_response(self.session, headers={"ETag": '"12345"'}) timestamp = self.client.get_records_timestamp() assert timestamp == '12345' def test_records_timestamp_is_cached(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"12345"'}) self.client.get_records() timestamp = self.client.get_records_timestamp() assert timestamp == '12345' assert self.session.request.call_count == 1 def test_records_timestamp_is_cached_per_collection(self): mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"12345"'}) self.client.get_records(collection="foo") mock_response(self.session, data=[{'id': 'foo'}, {'id': 'bar'}], headers={"ETag": '"67890"'}) self.client.get_records(collection="bar") timestamp = self.client.get_records_timestamp(collection="foo") assert timestamp == '12345' timestamp = self.client.get_records_timestamp(collection="bar") assert timestamp == '67890' def test_pagination_is_followed(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '5', 'value': 'item5'}, {'id': '6', 'value': 'item6'}, ], ), ] records = self.client.get_records(bucket='bucket', collection='collection') assert list(records) == [ {'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, {'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, {'id': '5', 'value': 'item5'}, {'id': '6', 'value': 'item6'}, ] def test_pagination_is_followed_for_number_of_pages(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '5', 'value': 'item5'}, {'id': '6', 'value': 'item6'}, ], ), ] records = self.client.get_records(bucket='bucket', collection='collection', pages=2) assert list(records) == [ {'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, {'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ] def test_pagination_is_not_followed_if_limit_is_specified(self): # Mock the calls to request. link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] records = self.client.get_records(bucket='bucket', collection='collection', _limit=2) assert list(records) == [ {'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'} ] def test_pagination_supports_if_none_match(self): link = ('http://example.org/buckets/buck/collections/coll/records/' '?token=1234') self.session.request.side_effect = [ # First one returns a list of items with a pagination token. build_response( [{'id': '1', 'value': 'item1'}, {'id': '2', 'value': 'item2'}, ], {'Next-Page': link}), # Second one returns a list of items without a pagination token. build_response( [{'id': '3', 'value': 'item3'}, {'id': '4', 'value': 'item4'}, ], ), ] self.client.get_records(bucket='bucket', collection='collection', if_none_match="1234") # Check that the If-None-Match header is present in the requests. self.session.request.assert_any_call( 'get', '/buckets/bucket/collections/collection/records', headers={'If-None-Match': '"1234"'}, params={}) self.session.request.assert_any_call( 'get', link, headers={'If-None-Match': '"1234"'}, params={}) def test_collection_can_delete_a_record(self): mock_response(self.session, data={'id': 1234}) resp = self.client.delete_record(id=1234) assert resp == {'id': 1234} url = '/buckets/mybucket/collections/mycollection/records/1234' self.session.request.assert_called_with('delete', url, headers=None) def test_record_delete_if_match(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record(collection='mycollection', bucket='mybucket', id='1', if_match=1234) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers={'If-Match': '"1234"'}) def test_record_delete_if_match_not_included_if_not_safe(self): data = {} mock_response(self.session, data=data) deleted = self.client.delete_record(collection='mycollection', bucket='mybucket', id='1', if_match=1234, safe=False) assert deleted == data url = '/buckets/mybucket/collections/mycollection/records/1' self.session.request.assert_called_with( 'delete', url, headers=None) def test_update_record_gets_the_id_from_data_if_exists(self): mock_response(self.session) self.client.update_record(bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=None, permissions=None) def test_update_record_handles_if_match(self): mock_response(self.session) self.client.update_record(bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}, if_match=1234) headers = {'If-Match': '"1234"'} self.session.request.assert_called_with( 'put', '/buckets/mybucket/collections/mycollection/records/1', data={'id': 1, 'foo': 'bar'}, headers=headers, permissions=None) def test_patch_record_uses_the_patch_method(self): mock_response(self.session) self.client.patch_record(bucket='mybucket', collection='mycollection', data={'id': 1, 'foo': 'bar'}) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', payload={'data': {'id': 1, 'foo': 'bar'}}, headers={"Content-Type": "application/json"}) def test_patch_record_recognizes_patchtype(self): mock_response(self.session) self.client.patch_record(bucket='mybucket', collection='mycollection', changes=MergePatch({'foo': 'bar'}, {'read': ['alice']}), id=1) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', payload={'data': {'foo': 'bar'}, 'permissions': {'read': ['alice']}}, headers={"Content-Type": "application/merge-patch+json"}, ) def test_patch_record_understands_jsonpatch(self): mock_response(self.session) self.client.patch_record( bucket='mybucket', collection='mycollection', changes=JSONPatch([{'op': 'add', 'patch': '/baz', 'value': 'qux'}]), id=1) self.session.request.assert_called_with( 'patch', '/buckets/mybucket/collections/mycollection/records/1', payload=[{'op': 'add', 'patch': '/baz', 'value': 'qux'}], headers={"Content-Type": "application/json-patch+json"}, ) def test_patch_record_requires_data_to_be_patch_type(self): with pytest.raises(TypeError, match="couldn't understand patch body 5"): self.client.patch_record(id=1, collection='testcoll', bucket='testbucket', changes=5) def test_patch_record_requires_id(self): with pytest.raises(KeyError, match="Unable to patch record, need an id."): self.client.patch_record(collection='testcoll', bucket='testbucket', data={}) def test_update_record_raises_if_no_id_is_given(self): with self.assertRaises(KeyError) as cm: self.client.update_record(data={'foo': 'bar'}, # Omit the id on purpose here. bucket='mybucket', collection='mycollection') assert str(cm.exception) == "'Unable to update a record, need an id.'" def test_get_or_create_doesnt_raise_in_case_of_conflict(self): data = { 'permissions': mock.sentinel.permissions, 'data': {'foo': 'bar'} } self.session.request.side_effect = [ get_http_error(status=412), (data, None) ] returned_data = self.client.create_record(bucket="buck", collection="coll", data={'id': 1234, 'foo': 'bar'}, if_not_exists=True) # Should not raise. assert returned_data == data def test_get_or_create_raise_in_other_cases(self): self.session.request.side_effect = get_http_error(status=500) with self.assertRaises(KintoException): self.client.create_record(bucket="buck", collection="coll", data={'foo': 'bar'}, id='record', if_not_exists=True) def test_create_record_raises_a_special_error_on_403(self): self.session.request.side_effect = get_http_error(status=403) with self.assertRaises(KintoException) as e: self.client.create_record(bucket="buck", collection="coll", data={'foo': 'bar'}) expected_msg = ("Unauthorized. Please check that the collection exists" " and that you have the permission to create or write " "on this collection record.") assert e.exception.message == expected_msg def test_create_record_can_deduce_id_from_data(self): self.client.create_record(data={'id': 'record'}, bucket='buck', collection='coll') self.session.request.assert_called_with( 'put', '/buckets/buck/collections/coll/records/record', data={'id': 'record'}, permissions=None, headers=DO_NOT_OVERWRITE) def test_update_record_can_deduce_id_from_data(self): self.client.update_record(data={'id': 'record'}, bucket='buck', collection='coll') self.session.request.assert_called_with( 'put', '/buckets/buck/collections/coll/records/record', data={'id': 'record'}, permissions=None, headers=None)
def validate_signature(event, context, **kwargs): """Validate the signature of each collection. """ server_url = event["server"] bucket = event.get("bucket", "monitor") collection = event.get("collection", "changes") client = Client(server_url=server_url, bucket=bucket, collection=collection) print("Read collection list from {}".format( client.get_endpoint("collection"))) error_messages = [] checked_certificates = {} collections = client.get_records() # Grab server data in parallel. start_time = time.time() collections_data = [] with concurrent.futures.ThreadPoolExecutor( max_workers=PARALLEL_REQUESTS) as executor: futures = [ executor.submit(download_collection_data, server_url, c) for c in collections ] for future in concurrent.futures.as_completed(futures): collections_data.append(future.result()) elapsed_time = time.time() - start_time print(f"Downloaded all data in {elapsed_time:.2f}s") for i, (collection, endpoint, metadata, records, timestamp) in enumerate(collections_data): start_time = time.time() message = "{:02d}/{:02d} {}: ".format(i + 1, len(collections), endpoint) # 1. Serialize serialized = canonical_json(records, timestamp) data = b"Content-Signature:\x00" + serialized.encode("utf-8") # 2. Grab the signature try: signature = metadata["signature"] except KeyError: # Destination has no signature attribute. # Be smart and check if it was just configured. # See https://github.com/mozilla-services/remote-settings-lambdas/issues/31 client = Client( server_url=server_url, bucket=collection["bucket"], collection=collection["collection"], ) with_tombstones = client.get_records(_since=1) if len(with_tombstones) == 0: # It never contained records. Let's assume it is newly configured. message += "SKIP" print(message) continue # Some records and empty signature? It will fail below. signature = {} try: # 3. Verify the signature with the public key pubkey = signature["public_key"].encode("utf-8") verifier = ecdsa.VerifyingKey.from_pem(pubkey) signature_bytes = base64.urlsafe_b64decode(signature["signature"]) verified = verifier.verify(signature_bytes, data, hashfunc=hashlib.sha384) assert verified, "Signature verification failed" # 4. Verify that the x5u certificate is valid (ie. that signature was well refreshed) x5u = signature["x5u"] if x5u not in checked_certificates: resp = requests.get(signature["x5u"]) cert_pem = resp.text.encode("utf-8") cert = cryptography.x509.load_pem_x509_certificate( cert_pem, crypto_default_backend()) assert (cert.not_valid_before < datetime.now()), "certificate not yet valid" assert cert.not_valid_after > datetime.now( ), "certificate expired" subject = cert.subject.get_attributes_for_oid( NameOID.COMMON_NAME)[0].value # eg. ``onecrl.content-signature.mozilla.org``, or # ``pinning-preload.content-signature.mozilla.org`` assert subject.endswith( ".content-signature.mozilla.org"), "invalid subject name" checked_certificates[x5u] = cert # 5. Check that public key matches the certificate one. cert = checked_certificates[x5u] cert_pubkey_pem = cert.public_key().public_bytes( crypto_serialization.Encoding.PEM, crypto_serialization.PublicFormat.SubjectPublicKeyInfo, ) assert (unpem(cert_pubkey_pem) == pubkey ), "signature public key does not match certificate" elapsed_time = time.time() - start_time message += f"OK ({elapsed_time:.2f}s)" print(message) except Exception: message += "⚠ BAD Signature ⚠" print(message) # Gather details for the global exception that will be raised. signed_on = metadata["last_modified"] signed_on_date = timestamp_to_date(signed_on) timestamp_date = timestamp_to_date(timestamp) error_message = ( "Signature verification failed on {endpoint}\n" " - Signed on: {signed_on} ({signed_on_date})\n" " - Records timestamp: {timestamp} ({timestamp_date})").format( **locals()) error_messages.append(error_message) # Make the lambda to fail in case an exception occured if len(error_messages) > 0: raise ValidationError("\n" + "\n\n".join(error_messages))
result += ',' result += '\n' i += 1 result += ']' return result for collection in collections: name = collection['id'] print(name + '...') record_list = client.get_records(collection=name, bucket='pesquisa') records = record_list[0]['info'] if len(record_list) > 1: print('found ' + str(len(record_list)) + ' entries.') for record in records: voto = Voto() voto.pesquisador = name voto.genero = record['genero'] voto.classe = record['classe_social'] voto.candidato = record['candidato'] voto.rejeita = record['rejeita']