def test_unpublish_reverts_changes_if_approval_fails( self, rs_urls, rs_settings, requestsmock): recipe = RecipeFactory(name="Test", approver=UserFactory()) baseline_record_url = rs_urls["workspace"]["baseline"][ "record"].format(recipe.id) capabilities_record_url = rs_urls["workspace"]["capabilities"][ "record"].format(recipe.id) # Deleting the record works. requestsmock.request("delete", baseline_record_url, json={"data": { "deleted": True }}) requestsmock.request("delete", capabilities_record_url, json={"data": { "deleted": True }}) requestsmock.request("patch", rs_urls["workspace"]["baseline"]["collection"], json={"data": {}}) requestsmock.register_uri( "patch", rs_urls["workspace"]["capabilities"]["collection"], [ # Approving fails. { "status_code": 403 }, # Rollback succeeds. { "status_code": 200, "json": { "data": {} } }, ], ) remotesettings = exports.RemoteSettings() with pytest.raises(kinto_http.KintoException): remotesettings.unpublish(recipe) requests = requestsmock.request_history assert len(requests) == 5 # Unpublish the recipe in both collections assert requests[0].url == capabilities_record_url assert requests[0].method == "DELETE" assert requests[1].url == baseline_record_url assert requests[1].method == "DELETE" # Try (and fail) to approve the capabilities change assert requests[2].url == rs_urls["workspace"]["capabilities"][ "collection"] assert requests[2].method == "PATCH" # so it rollsback both collections assert requests[3].method == "PATCH" assert requests[3].url == rs_urls["workspace"]["capabilities"][ "collection"] assert requests[4].method == "PATCH" assert requests[4].url == rs_urls["workspace"]["baseline"][ "collection"]
def test_cant_change_signature_and_other_fields(self): recipe = RecipeFactory(name="unchanged", signed=False) recipe.signature = SignatureFactory() with pytest.raises(ValidationError) as exc_info: recipe.revise(name="changed") assert exc_info.value.message == "Signatures must change alone"
def test_recipe_force_revise(self): recipe = RecipeFactory(name="my name") revision_id = recipe.revision_id recipe.revise(name="my name", force=True) assert revision_id != recipe.revision_id
def console_log(message, **kwargs): return RecipeFactory(action=Action.objects.get(name='console-log'), arguments={'message': message}, **kwargs)
def test_filter_expression(self): r = RecipeFactory(extra_filter_expression="", filter_object_json=None) assert r.filter_expression == "" r = RecipeFactory(extra_filter_expression="2 + 2 == 4", filter_object_json=None) assert r.filter_expression == "2 + 2 == 4"
def test_it_serves_revisions(self, api_client): recipe = RecipeFactory() res = api_client.get('/api/v2/recipe_revision/%s/' % recipe.latest_revision.id) assert res.status_code == 200 assert res.data['id'] == recipe.latest_revision.id
def test_revision_id_changes(self): """Ensure that the revision id is incremented on each save""" recipe = RecipeFactory() revision_id = recipe.revision_id recipe.revise(action=ActionFactory()) assert recipe.revision_id != revision_id
def test_it_works(self, api_client): recipe = RecipeFactory() res = api_client.get(f"/api/v1/recipe/{recipe.id}/") assert res.status_code == 200, res.data assert res.data["id"] == recipe.id
def test_it_unsigns_disabled_recipes(self, mocked_autograph): r = RecipeFactory(approver=UserFactory(), signed=True) call_command("update_recipe_signatures") r.refresh_from_db() assert r.signature is None
def test_signed_false(self): r = RecipeFactory(signed=False) assert r.signature is None
def test_signed_true(self): r = RecipeFactory(approver=UserFactory(), signed=True) assert r.signature is not None assert r.signature.signature == hashlib.sha256( r.canonical_json()).hexdigest() assert isinstance(r.signature.timestamp, datetime)
def test_it_unsigns_disabled_recipes(self, mocked_autograph): r = RecipeFactory(enabled=False, signed=True) call_command('update_recipe_signatures') r.refresh_from_db() assert r.signature is None
def test_it_signs_unsigned_enabled_recipes(self, mocked_autograph): r = RecipeFactory(approver=UserFactory(), enabled=True, signed=False) call_command('update_recipe_signatures') r.refresh_from_db() assert r.signature is not None
def test_publish_and_unpublish_baseline_recipe_to_both_collections( self, rs_settings, rs_urls, requestsmock): ws_urls = rs_urls["workspace"] recipe = RecipeFactory() rs_settings.BASELINE_CAPABILITIES |= recipe.capabilities assert recipe.uses_only_baseline_capabilities() # Expect publish calls to both collections requestsmock.put(ws_urls["baseline"]["record"].format(recipe.id), json={"data": {}}, status_code=201) requestsmock.put(ws_urls["capabilities"]["record"].format(recipe.id), json={"data": {}}, status_code=201) # Expect both workspaces to be approved requestsmock.patch(ws_urls["baseline"]["collection"], json={"data": {}}) requestsmock.patch(ws_urls["capabilities"]["collection"], json={"data": {}}) remotesettings = exports.RemoteSettings() remotesettings.publish(recipe) requests = requestsmock.request_history assert len(requests) == 4 # First it publishes a recipe to both collections assert requests[0].method == "PUT" assert requests[0].url == ws_urls["capabilities"]["record"].format( recipe.id) assert requests[1].method == "PUT" assert requests[1].url == ws_urls["baseline"]["record"].format( recipe.id) # and then approves both changes assert requests[2].method == "PATCH" assert requests[2].url == rs_urls["workspace"]["capabilities"][ "collection"] assert requests[3].method == "PATCH" assert requests[3].url == rs_urls["workspace"]["baseline"][ "collection"] # reset request history requestsmock._adapter.request_history = [] # Expect delete calls requestsmock.delete(ws_urls["baseline"]["record"].format(recipe.id), json={"data": {}}) requestsmock.delete(ws_urls["capabilities"]["record"].format( recipe.id), json={"data": {}}) remotesettings.unpublish(recipe) requests = requestsmock.request_history assert len(requests) == 4 # First it removes the recipe from both collections assert requests[0].method == "DELETE" assert requests[0].url == ws_urls["capabilities"]["record"].format( recipe.id) assert requests[1].method == "DELETE" assert requests[1].url == ws_urls["baseline"]["record"].format( recipe.id) # and then approves both changes assert requests[2].method == "PATCH" assert requests[2].url == rs_urls["workspace"]["capabilities"][ "collection"] assert requests[3].method == "PATCH" assert requests[3].url == rs_urls["workspace"]["baseline"][ "collection"]
def test_recipe_force_update(self): recipe = RecipeFactory(name='my name') revision_id = recipe.revision_id recipe.update(name='my name', force=True) assert revision_id != recipe.revision_id
def test_latest_revision_changes(self): """Ensure that a new revision is created on each save""" recipe = RecipeFactory() revision_id = recipe.latest_revision.id recipe.revise(action=ActionFactory()) assert recipe.latest_revision.id != revision_id
def test_detail_sets_no_cookies(self, api_client): recipe = RecipeFactory() res = api_client.get('/api/v2/recipe/{id}/'.format(id=recipe.id)) assert res.status_code == 200 assert res.client.cookies == {}
def test_uses_extra_capabilities(self): recipe = RecipeFactory(extra_capabilities=["test.foo", "test.bar"]) assert "test.foo" in recipe.latest_revision.capabilities assert "test.bar" in recipe.latest_revision.capabilities
def test_it_serves_recipes(self, api_client): recipe = RecipeFactory() res = api_client.get('/api/v2/recipe/') assert res.status_code == 200 assert res.data['results'][0]['name'] == recipe.name
def test_action_name_is_automatically_included(self): action = ActionFactory() recipe = RecipeFactory(action=action) assert set(action.capabilities) <= set( recipe.latest_revision.capabilities)
def test_revise_arguments(self): recipe = RecipeFactory(arguments_json="[]") recipe.revise(arguments=[{"id": 1}]) assert recipe.arguments_json == '[{"id": 1}]'
def test_filter_object_capabilities_are_automatically_included(self): filter_object = StableSampleFilter.create(input=["A"], rate=0.1) recipe = RecipeFactory(filter_object=[filter_object]) assert filter_object.capabilities assert filter_object.capabilities <= recipe.latest_revision.capabilities
def show_heartbeat(**kwargs): return RecipeFactory(action=Action.objects.get(name='show-heartbeat'), **kwargs)
def test_it_serves_recipes(self, api_client): recipe = RecipeFactory() res = api_client.get("/api/v1/recipe/") assert res.status_code == 200 assert res.data[0]["name"] == recipe.name
def test_signature_is_correct_on_creation_if_autograph_available(self, mocked_autograph): recipe = RecipeFactory(approver=UserFactory(), enabler=UserFactory()) expected_sig = fake_sign([recipe.canonical_json()])[0]["signature"] assert recipe.signature.signature == expected_sig
def test_signature_is_correct_on_creation_if_autograph_available( self, mocked_autograph): recipe = RecipeFactory() expected_sig = hashlib.sha256(recipe.canonical_json()).hexdigest() assert recipe.signature.signature == expected_sig
def test_recipe_revise_arguments(self): recipe = RecipeFactory(arguments_json="{}") recipe.revise(arguments={"something": "value"}) assert recipe.arguments_json == '{"something": "value"}'
def test_recipe_update_arguments(self): recipe = RecipeFactory(arguments_json='') recipe.update(arguments={'something': 'value'}) assert recipe.arguments_json == '{"something": "value"}'
def test_update_logging(self, mock_logger): recipe = RecipeFactory(name="my name") recipe.revise(name="my name", force=True) mock_logger.info.assert_called_with( Whatever.contains(str(recipe.id)), extra={"code": INFO_CREATE_REVISION} )
def test_resolve_all_recipes(self, gql_client): r = RecipeFactory() res = gql_client.execute(GQ().query.allRecipes.fields("id")) assert res == {"data": {"allRecipes": [{"id": str(r.id)}]}}