Ejemplo n.º 1
0
    def test_recipe_as_remotesettings_record(self, mocked_autograph):
        """Test that recipes are serialized as expected by our clients."""

        recipe = RecipeFactory(name="Test",
                               approver=UserFactory(),
                               enabler=UserFactory(),
                               signed=True)

        record = exports.recipe_as_record(recipe)
        assert record == {
            "id": str(recipe.id),
            "recipe": {
                "action": recipe.action.name,
                "arguments": recipe.arguments,
                "filter_expression": recipe.filter_expression,
                "id": recipe.id,
                "name": recipe.name,
                "revision_id": str(recipe.revision_id),
            },
            "signature": {
                "public_key": Whatever.regex(r"[a-zA-Z0-9/+]{160}"),
                "signature": Whatever.regex(r"[a-f0-9]{40}"),
                "timestamp": Whatever.iso8601(),
                "x5u": Whatever.startswith("https://"),
            },
        }
Ejemplo n.º 2
0
    def test_it_works(self, rf):
        recipe = RecipeFactory(arguments={"foo": "bar"})
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={"request": rf.get("/")})

        assert serializer.data == {
            "name": recipe.name,
            "id": recipe.id,
            "last_updated": Whatever(),
            "enabled": recipe.enabled,
            "extra_filter_expression": recipe.extra_filter_expression,
            "filter_expression": recipe.filter_expression,
            "revision_id": recipe.revision_id,
            "action": action.name,
            "arguments": {
                "foo": "bar"
            },
            "is_approved": False,
            "latest_revision_id": recipe.latest_revision.id,
            "approved_revision_id": None,
            "approval_request": {
                "id": approval.id,
                "created": Whatever(),
                "creator": Whatever(),
                "approved": None,
                "approver": None,
                "comment": None,
            },
            "identicon_seed": Whatever.startswith("v1:"),
            "capabilities": sorted(recipe.capabilities),
        }
Ejemplo n.º 3
0
    def test_update_signatures(self, mocker, mock_logger):
        # Make sure the test environment is clean. This test is invalid otherwise.
        assert Recipe.objects.all().count() == 0

        # Mock the Autographer
        mock_autograph = mocker.patch('normandy.recipes.models.Autographer')
        mock_autograph.return_value.sign_data.return_value = [
            {
                'signature': 'fake signature 1'
            },
            {
                'signature': 'fake signature 2'
            },
        ]

        # Make and sign two recipes
        (recipe1, recipe2) = RecipeFactory.create_batch(2)
        Recipe.objects.all().update_signatures()

        # Assert that the signature update is logged.
        mock_logger.info.assert_called_with(
            Whatever.contains(str(recipe1.id), str(recipe2.id)),
            extra={
                'code': INFO_REQUESTING_RECIPE_SIGNATURES,
                'recipe_ids': Whatever.contains(recipe1.id, recipe2.id)
            })

        # Assert the autographer was used as expected
        assert mock_autograph.called
        assert mock_autograph.return_value.sign_data.called_with(
            [Whatever(), Whatever()])
        signatures = list(Recipe.objects.all().values_list(
            'signature__signature', flat=True))
        assert signatures == ['fake signature 1', 'fake signature 2']
Ejemplo n.º 4
0
    def test_update_signatures(self, mocker, mock_logger):
        # Make sure the test environment is clean. This test is invalid otherwise.
        assert Action.objects.all().count() == 0

        # Mock the Autographer
        mock_autograph = mocker.patch('normandy.recipes.models.Autographer')
        mock_autograph.return_value.sign_data.return_value = [
            {
                'signature': 'fake signature 1'
            },
            {
                'signature': 'fake signature 2'
            },
        ]

        # Make and sign two actions
        (action1, action2) = ActionFactory.create_batch(2)
        Action.objects.all().update_signatures()

        # Assert that the signature update is logged.
        mock_logger.info.assert_called_with(
            Whatever.contains(action1.name, action2.name),
            extra={
                'code': INFO_REQUESTING_ACTION_SIGNATURES,
                'action_names': Whatever.contains(action1.name, action2.name),
            })

        # Assert the autographer was used as expected
        assert mock_autograph.called
        assert mock_autograph.return_value.sign_data.called_with(
            [Whatever(), Whatever()])
        signatures = list(Action.objects.all().values_list(
            'signature__signature', flat=True))
        assert signatures == ['fake signature 1', 'fake signature 2']
Ejemplo n.º 5
0
    def test_forwards(self, migrations):
        # Get the pre-migration models
        old_apps = migrations.migrate("recipes", "0006_reciperevision_filter_object_json")
        Recipe = old_apps.get_model("recipes", "Recipe")
        Action = old_apps.get_model("recipes", "Action")
        RecipeRevision = old_apps.get_model("recipes", "RecipeRevision")
        Channel = old_apps.get_model("recipes", "Channel")
        Country = old_apps.get_model("recipes", "Country")
        Locale = old_apps.get_model("recipes", "Locale")

        # Create test data
        recipe = Recipe.objects.create()
        action = Action.objects.create()
        channel1 = Channel.objects.create(slug="beta")
        channel2 = Channel.objects.create(slug="release")
        country1 = Country.objects.create(code="US")
        country2 = Country.objects.create(code="CA")
        locale1 = Locale.objects.create(code="en-US")
        locale2 = Locale.objects.create(code="fr-CA")

        revision = RecipeRevision.objects.create(
            recipe=recipe, action=action, name="Test Revision", identicon_seed="v1:test"
        )
        revision.channels.set([channel1, channel2])
        revision.countries.set([country1, country2])
        revision.locales.set([locale1, locale2])
        revision.save()

        # Apply the migration
        new_apps = migrations.migrate("recipes", "0007_convert_simple_filters_to_filter_objects")

        # Get the post-migration models
        RecipeRevision = new_apps.get_model("recipes", "RecipeRevision")

        # Fetch the revision
        revision = RecipeRevision.objects.get()
        revision.filter_object = json.loads(revision.filter_object_json)

        # All simple filters should be removed
        assert revision.channels.count() == 0
        assert revision.countries.count() == 0
        assert revision.locales.count() == 0

        # Order and duplication don't matter, so index the filter by type, and
        # compare the inner values using sets
        assert len(revision.filter_object) == 3
        filters_by_type = {f["type"]: f for f in revision.filter_object}
        assert filters_by_type["channel"] == {
            "type": "channel",
            "channels": Whatever(lambda v: set(v) == {channel1.slug, channel2.slug}),
        }
        assert filters_by_type["country"] == {
            "type": "country",
            "countries": Whatever(lambda v: set(v) == {country1.code, country2.code}),
        }
        assert filters_by_type["locale"] == {
            "type": "locale",
            "locales": Whatever(lambda v: set(v) == {locale1.code, locale2.code}),
        }
Ejemplo n.º 6
0
    def test_it_works(self):
        signature = SignatureFactory()
        serializer = SignatureSerializer(instance=signature)

        assert serializer.data == {
            "signature": Whatever.regex(r"[a-f0-9]{40}"),
            "x5u": Whatever.startswith(signature.x5u),
            "timestamp": Whatever.iso8601(),
            "public_key": Whatever.regex(r"[a-zA-Z0-9/+]{160}"),
        }
Ejemplo n.º 7
0
    def test_it_works(self):
        signature = SignatureFactory()
        serializer = SignatureSerializer(instance=signature)

        assert serializer.data == {
            "signature": Whatever.regex(r"[a-f0-9]{40}"),
            "x5u": Whatever.startswith(signature.x5u),
            "timestamp": Whatever.iso8601(),
            "public_key": Whatever.regex(r"[a-zA-Z0-9/+]{160}"),
        }
Ejemplo n.º 8
0
    def test_it_works(self):
        signature = SignatureFactory()
        serializer = SignatureSerializer(instance=signature)

        assert serializer.data == {
            'signature': Whatever.regex(r'[a-f0-9]{40}'),
            'x5u': Whatever.startswith(signature.x5u),
            'timestamp': Whatever.iso8601(),
            'public_key': Whatever.regex(r'[a-zA-Z0-9/+]{160}')
        }
Ejemplo n.º 9
0
 def test_update_signature(self, mock_logger, mocked_autograph):
     recipe = RecipeFactory(enabler=UserFactory(), approver=UserFactory())
     recipe.signature = None
     recipe.update_signature()
     mock_logger.info.assert_called_with(
         Whatever.contains(str(recipe.id)),
         extra={"code": INFO_REQUESTING_RECIPE_SIGNATURES, "recipe_ids": [recipe.id]},
     )
     mocked_autograph.return_value.sign_data.assert_called_with(
         [Whatever(lambda s: json.loads(s)["id"] == recipe.id)]
     )
     assert recipe.signature is not None
Ejemplo n.º 10
0
def test_bundle_serializer(rf):
    recipe = RecipeFactory()
    bundle = BundleFactory(recipes=[recipe])
    serializer = BundleSerializer(bundle, context={'request': rf.get('/')})

    assert serializer.data['recipes'] == [{
        'name': recipe.name,
        'id': recipe.id,
        'revision_id': recipe.revision_id,
        'action': Whatever(),
        'arguments': Whatever(),
    }]
Ejemplo n.º 11
0
    def test_it_works(self):
        author = UserFactory()
        reviewer = UserFactory()

        recipe = RecipeFactory(name="first", user=author)
        revision1 = recipe.latest_revision
        approval_request = revision1.request_approval(author)
        approval_request.approve(reviewer, "r+")
        revision1.enable(author)
        state1 = revision1.enabled_state
        assert state1 is not None

        recipe.revise("second", user=author)
        revision2 = recipe.latest_revision
        approval_request = revision2.request_approval(author)
        approval_request.approve(reviewer, "r+")
        state2 = revision2.enabled_state
        assert state2 is not None

        serializer = EnabledStateSerializer(state1)
        assert serializer.data == {
            "id": state1.id,
            "carryover_from": None,
            "created": Whatever.iso8601(),
            "creator": {
                "id": author.id,
                "first_name": author.first_name,
                "last_name": author.last_name,
                "email": author.email,
            },
            "enabled": True,
            "revision_id": revision1.id,
        }

        serializer = EnabledStateSerializer(state2)
        assert serializer.data == {
            "id": state2.id,
            "carryover_from": state1.id,
            "created": Whatever.iso8601(),
            "creator": {
                "id": reviewer.id,
                "first_name": reviewer.first_name,
                "last_name": reviewer.last_name,
                "email": reviewer.email,
            },
            "enabled": True,
            "revision_id": revision2.id,
        }
Ejemplo n.º 12
0
    def test_it_works(self):
        author = UserFactory()
        reviewer = UserFactory()

        recipe = RecipeFactory(name="first", user=author)
        revision1 = recipe.latest_revision
        approval_request = revision1.request_approval(author)
        approval_request.approve(reviewer, "r+")
        revision1.enable(author)
        state1 = revision1.enabled_state
        assert state1 is not None

        recipe.revise("second", user=author)
        revision2 = recipe.latest_revision
        approval_request = revision2.request_approval(author)
        approval_request.approve(reviewer, "r+")
        state2 = revision2.enabled_state
        assert state2 is not None

        serializer = EnabledStateSerializer(state1)
        assert serializer.data == {
            "id": state1.id,
            "carryover_from": None,
            "created": Whatever.iso8601(),
            "creator": {
                "id": author.id,
                "first_name": author.first_name,
                "last_name": author.last_name,
                "email": author.email,
            },
            "enabled": True,
            "revision_id": revision1.id,
        }

        serializer = EnabledStateSerializer(state2)
        assert serializer.data == {
            "id": state2.id,
            "carryover_from": state1.id,
            "created": Whatever.iso8601(),
            "creator": {
                "id": reviewer.id,
                "first_name": reviewer.first_name,
                "last_name": reviewer.last_name,
                "email": reviewer.email,
            },
            "enabled": True,
            "revision_id": revision2.id,
        }
Ejemplo n.º 13
0
    def test_it_logs_when_geoip_fails(self, geolocation, mocker, mock_logger):
        mock_reader = mocker.patch('normandy.recipes.geolocation.geoip_reader')
        mock_reader.country.side_effect = GeoIP2Error()

        assert geolocation.get_country_code('207.126.102.129') is None
        mock_logger.warning.assert_called_with(
            Whatever(), extra={'code': WARNING_UNKNOWN_GEOIP_ERROR})
Ejemplo n.º 14
0
    def test_it_warns_when_cant_load_database(self, mocker, mock_logger):
        MockReader = mocker.patch('normandy.recipes.geolocation.Reader')
        MockReader.side_effect = IOError()

        load_geoip_database()
        mock_logger.warning.assert_called_with(
            Whatever(), extra={'code': WARNING_CANNOT_LOAD_DATABASE})
Ejemplo n.º 15
0
    def test_it_works_with_no_signature(self, rf):
        recipe = RecipeFactory(signed=False)
        action = recipe.action
        serializer = SignedRecipeSerializer(instance=recipe,
                                            context={'request': rf.get('/')})

        assert serializer.data == {
            'signature': None,
            'recipe': {
                'name': recipe.name,
                'id': recipe.id,
                'enabled': recipe.enabled,
                'extra_filter_expression': recipe.extra_filter_expression,
                'filter_expression': recipe.filter_expression,
                'revision_id': recipe.revision_id,
                'action': action.name,
                'arguments': recipe.arguments,
                'last_updated': Whatever(),
                'channels': [],
                'countries': [],
                'locales': [],
                'is_approved': False,
                'latest_revision_id': recipe.latest_revision.id,
                'approved_revision_id': recipe.approved_revision_id,
                'approval_request': None,
            }
        }
Ejemplo n.º 16
0
    def test_it_serves_recipes(self, client):
        recipe = RecipeFactory(arguments={'foo': 'bar'})
        data = ClientParametersFactory()
        res = client.post('/api/v1/fetch_bundle/', data)

        action = recipe.action
        impl_url = reverse('recipes:action-implementation',
                           kwargs={
                               'name': action.name,
                               'impl_hash': action.implementation_hash,
                           })
        assert res.status_code == 200
        assert res.data['recipes'] == [{
            'name': recipe.name,
            'id': recipe.id,
            'revision_id': recipe.revision_id,
            'action': {
                'name': action.name,
                'implementation_url': Whatever.endswith(impl_url),
                'arguments_schema': action.arguments_schema,
            },
            'arguments': {
                'foo': 'bar',
            },
        }]
Ejemplo n.º 17
0
    def test_it_works(self, rf):
        recipe = RecipeFactory(arguments={"foo": "bar"})
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={"request": rf.get("/")})

        assert serializer.data == {
            "name": recipe.name,
            "id": recipe.id,
            "last_updated": Whatever(),
            "enabled": recipe.enabled,
            "extra_filter_expression": recipe.extra_filter_expression,
            "filter_expression": recipe.filter_expression,
            "revision_id": recipe.revision_id,
            "action": action.name,
            "arguments": {"foo": "bar"},
            "is_approved": False,
            "latest_revision_id": recipe.latest_revision.id,
            "approved_revision_id": None,
            "approval_request": {
                "id": approval.id,
                "created": Whatever(),
                "creator": Whatever(),
                "approved": None,
                "approver": None,
                "comment": None,
            },
            "identicon_seed": Whatever.startswith("v1:"),
        }
Ejemplo n.º 18
0
    def test_it_works_with_signature(self, rf):
        recipe = RecipeFactory(approver=UserFactory(), signed=True)
        context = {"request": rf.get("/")}
        combined_serializer = SignedRecipeSerializer(instance=recipe, context=context)
        recipe_serializer = MinimalRecipeSerializer(instance=recipe, context=context)

        # Testing for shape of data, not contents
        assert combined_serializer.data == {
            "signature": {
                "signature": Whatever(),
                "timestamp": Whatever(),
                "x5u": Whatever(),
                "public_key": Whatever(),
            },
            "recipe": recipe_serializer.data,
        }
Ejemplo n.º 19
0
    def test_it_works(self, rf):
        channel = ChannelFactory()
        country = CountryFactory()
        locale = LocaleFactory()
        recipe = RecipeFactory(arguments={'foo': 'bar'},
                               channels=[channel],
                               countries=[country],
                               locales=[locale])
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={'request': rf.get('/')})

        assert serializer.data == {
            'name': recipe.name,
            'id': recipe.id,
            'last_updated': Whatever(),
            'enabled': recipe.enabled,
            'extra_filter_expression': recipe.extra_filter_expression,
            'filter_expression': recipe.filter_expression,
            'revision_id': recipe.revision_id,
            'action': action.name,
            'arguments': {
                'foo': 'bar',
            },
            'channels': [channel.slug],
            'countries': [country.code],
            'locales': [locale.code]
        }
Ejemplo n.º 20
0
 def test_it_doesnt_pass_the_api_root_url_to_the_api_root_view(
         self, mocker):
     mock_api_view = mocker.Mock()
     router = MixedViewRouter(view=mock_api_view)
     router.register_view('view', View, name='standalone-view')
     router.get_urls()
     assert mock_api_view.called_once_with(
         [Whatever(lambda v: v.name == 'standalone-view')])
Ejemplo n.º 21
0
 def test_log_failure_no_username(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = None
     request = RequestFactory().get("/")
     user = LoggingModelBackend().authenticate(request, password="******")
     assert user is None
     mock_logger.warning.assert_called_with(
         Whatever.contains("no username provided"), extra={"code": WARNING_LOGIN_FAILURE}
     )
Ejemplo n.º 22
0
    def test_it_works_with_signature(self, rf):
        recipe = RecipeFactory(signed=True)
        context = {'request': rf.get('/')}
        combined_serializer = SignedRecipeSerializer(instance=recipe,
                                                     context=context)
        recipe_serializer = RecipeSerializer(instance=recipe, context=context)

        # Testing for shape of data, not contents
        assert combined_serializer.data == {
            'signature': {
                'signature': Whatever(),
                'timestamp': Whatever(),
                'x5u': Whatever(),
                'public_key': Whatever(),
            },
            'recipe': recipe_serializer.data,
        }
Ejemplo n.º 23
0
 def test_log_success(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = True
     user = LoggingModelBackend().authenticate(username='******', password='******')
     assert user
     mock_logger.info.assert_called_with(
         Whatever.contains('fakeuser'),
         extra={'code': INFO_LOGIN_SUCCESS}
     )
Ejemplo n.º 24
0
 def test_log_failure_no_username(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = None
     user = LoggingModelBackend().authenticate(password='******')
     assert user is None
     mock_logger.warning.assert_called_with(
         Whatever.contains('no username provided'),
         extra={'code': WARNING_LOGIN_FAILURE}
     )
Ejemplo n.º 25
0
 def test_log_success(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = True
     request = RequestFactory().get("/")
     user = LoggingModelBackend().authenticate(request,
                                               username="******",
                                               password="******")
     assert user
     mock_logger.info.assert_called_with(Whatever.contains("fakeuser"),
                                         extra={"code": INFO_LOGIN_SUCCESS})
Ejemplo n.º 26
0
 def test_log_failure_no_username(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = None
     request = RequestFactory().get("/")
     user = LoggingModelBackend().authenticate(request,
                                               password="******")
     assert user is None
     mock_logger.warning.assert_called_with(
         Whatever.contains("no username provided"),
         extra={"code": WARNING_LOGIN_FAILURE})
Ejemplo n.º 27
0
    def test_it_works(self, rf):
        channel = ChannelFactory()
        country = CountryFactory()
        locale = LocaleFactory()
        recipe = RecipeFactory(arguments={'foo': 'bar'},
                               channels=[channel],
                               countries=[country],
                               locales=[locale])
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={'request': rf.get('/')})

        assert serializer.data == {
            'name': recipe.name,
            'id': recipe.id,
            'last_updated': Whatever(),
            'enabled': recipe.enabled,
            'extra_filter_expression': recipe.extra_filter_expression,
            'filter_expression': recipe.filter_expression,
            'action': {
                'arguments_schema': {},
                'id': action.id,
                'implementation_url': Whatever(),
                'name': action.name,
            },
            'arguments': {
                'foo': 'bar',
            },
            'channels': [channel.slug],
            'countries': [country.code],
            'locales': [locale.code],
            'is_approved': False,
            'latest_revision':
            RecipeRevisionSerializer(recipe.latest_revision).data,
            'approved_revision': None,
            'approval_request': {
                'id': approval.id,
                'created': Whatever(),
                'creator': Whatever(),
                'approved': None,
                'approver': None,
                'comment': None,
            },
        }
Ejemplo n.º 28
0
 def test_log_success(self, mock_logger, mock_authenticate):
     mock_authenticate.return_value = True
     request = RequestFactory().get("/")
     user = LoggingModelBackend().authenticate(
         request, username="******", password="******"
     )
     assert user
     mock_logger.info.assert_called_with(
         Whatever.contains("fakeuser"), extra={"code": INFO_LOGIN_SUCCESS}
     )
Ejemplo n.º 29
0
    def test_it_serves_extensions(self, api_client, storage):
        extension = ExtensionFactory(name="foo")

        res = api_client.get("/api/v3/extension/")
        assert res.status_code == 200
        assert res.data["results"] == [{
            "id": extension.id,
            "name": "foo",
            "xpi": Whatever()
        }]
Ejemplo n.º 30
0
    def test_it_serves_extensions(self, api_client):
        extension = ExtensionFactory(name='foo', )

        res = api_client.get('/api/v2/extension/')
        assert res.status_code == 200
        assert res.data['results'] == [{
            'id': extension.id,
            'name': 'foo',
            'xpi': Whatever(),
        }]
Ejemplo n.º 31
0
    def test_update_locales(self, tmpdir, mock_logger):
        assert Locale.objects.count() == 0

        storage = ProductDetailsRelationalStorage(json_dir=tmpdir.strpath)
        storage.update('languages.json', LANGUAGES_JSON, '1999-01-01')

        mock_logger.info.assert_called_with(
            Whatever(), extra={'code': INFO_UPDATE_PRODUCT_DETAILS})
        assert Locale.objects.count() == 12
        assert Locale.objects.filter(code='en-US',
                                     name='English (US)').exists()
Ejemplo n.º 32
0
 def test_password_not_queryable(self, gql_client):
     u = UserFactory()
     res = gql_client.execute(GQ().query.user(id=u.id).fields("password"))
     assert res == {
         "errors": [{
             "locations":
             Whatever(),
             "message":
             'Cannot query field "password" on type "UserType".',
         }]
     }
Ejemplo n.º 33
0
 def test_update_signature(self, mock_logger, mocked_autograph):
     recipe = RecipeFactory(enabler=UserFactory(), approver=UserFactory())
     recipe.signature = None
     recipe.update_signature()
     mock_logger.info.assert_called_with(
         Whatever.contains(str(recipe.id)),
         extra={"code": INFO_REQUESTING_RECIPE_SIGNATURES, "recipe_ids": [recipe.id]},
     )
     mocked_autograph.return_value.sign_data.assert_called_with(
         [Whatever(lambda s: json.loads(s)["id"] == recipe.id)]
     )
     assert recipe.signature is not None
Ejemplo n.º 34
0
    def test_it_does_not_send_excessive_remote_settings_traffic(
        self, mocker, settings, mocked_autograph
    ):
        # 10 to update
        recipes = RecipeFactory.create_batch(
            10, approver=UserFactory(), enabler=UserFactory(), signed=False
        )
        assert all(not r.approved_revision.uses_only_baseline_capabilities() for r in recipes)

        # Set up a version of the Remote Settings helper with a mocked out client
        client_mock = None

        def rs_with_mocked_client():
            nonlocal client_mock
            assert client_mock is None
            rs = exports.RemoteSettings()
            client_mock = mocker.MagicMock()
            rs.client = client_mock
            return rs

        mocker.patch(
            "normandy.recipes.management.commands.update_recipe_signatures.RemoteSettings",
            side_effect=rs_with_mocked_client,
        )

        call_command("update_recipe_signatures")

        # Make sure that our mock was actually used
        assert client_mock

        # One signing request to the capabilities collection
        assert client_mock.patch_collection.mock_calls == [
            mocker.call(
                id=settings.REMOTE_SETTINGS_CAPABILITIES_COLLECTION_ID,
                data={"status": "to-sign"},
                bucket=settings.REMOTE_SETTINGS_WORKSPACE_BUCKET_ID,
            )
        ]

        # one publish to the capabilities collection per recipe
        expected_calls = []
        for recipe in recipes:
            expected_calls.append(
                mocker.call(
                    data=Whatever(lambda r: r["id"] == recipe.id, name=f"Recipe {recipe.id}"),
                    bucket=settings.REMOTE_SETTINGS_WORKSPACE_BUCKET_ID,
                    collection=settings.REMOTE_SETTINGS_CAPABILITIES_COLLECTION_ID,
                )
            )
        client_mock.update_record.has_calls(expected_calls, any_order=True)  # all expected calls
        assert client_mock.update_record.call_count == len(expected_calls)  # no extra calls
Ejemplo n.º 35
0
    def test_it_works(self, rf):
        recipe = RecipeFactory(arguments={"foo": "bar"},
                               filter_object_json=None)
        approval = ApprovalRequestFactory(revision=recipe.latest_revision)
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={"request": rf.get("/")})

        assert serializer.data == {
            "name": recipe.name,
            "id": recipe.id,
            "last_updated": Whatever(),
            "enabled": recipe.enabled,
            "extra_filter_expression": recipe.extra_filter_expression,
            "filter_expression": recipe.filter_expression,
            "filter_object": [],
            "action": {
                "arguments_schema": {},
                "id": action.id,
                "implementation_url": Whatever(),
                "name": action.name,
            },
            "arguments": {
                "foo": "bar"
            },
            "is_approved": False,
            "latest_revision":
            RecipeRevisionSerializer(recipe.latest_revision).data,
            "approved_revision": None,
            "approval_request": {
                "id": approval.id,
                "created": Whatever(),
                "creator": Whatever(),
                "approved": None,
                "approver": None,
                "comment": None,
            },
            "identicon_seed": Whatever.startswith("v1:"),
        }
Ejemplo n.º 36
0
    def test_update_signature(self, mocker, mock_logger):
        # Mock the Autographer
        mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
        mock_autograph.return_value.sign_data.return_value = [{"signature": "fake signature"}]

        action = ActionFactory(signed=False)
        action.update_signature()
        mock_logger.info.assert_called_with(
            Whatever.contains(action.name),
            extra={"code": INFO_REQUESTING_ACTION_SIGNATURES, "action_names": [action.name]},
        )

        action.save()
        assert action.signature is not None
        assert action.signature.signature == "fake signature"
Ejemplo n.º 37
0
    def test_update_signature(self, mocker, mock_logger):
        # Mock the Autographer
        mock_autograph = mocker.patch("normandy.recipes.models.Autographer")
        mock_autograph.return_value.sign_data.return_value = [{"signature": "fake signature"}]

        action = ActionFactory(signed=False)
        action.update_signature()
        mock_logger.info.assert_called_with(
            Whatever.contains(action.name),
            extra={"code": INFO_REQUESTING_ACTION_SIGNATURES, "action_names": [action.name]},
        )

        action.save()
        assert action.signature is not None
        assert action.signature.signature == "fake signature"
Ejemplo n.º 38
0
    def test_it_serves_actions(self, api_client):
        action = ActionFactory(
            name="foo", implementation="foobar", arguments_schema={"type": "object"}
        )

        res = api_client.get("/api/v1/action/")
        action_url = reverse(
            "recipes:v1:action-implementation",
            kwargs={"name": action.name, "impl_hash": action.implementation_hash},
        )
        assert res.status_code == 200
        assert res.data == [
            {
                "name": "foo",
                "implementation_url": Whatever.endswith(action_url),
                "arguments_schema": {"type": "object"},
            }
        ]
Ejemplo n.º 39
0
    def test_it_serves_actions(self, api_client):
        action = ActionFactory(
            name='foo',
            implementation='foobar',
            arguments_schema={'type': 'object'}
        )

        res = api_client.get('/api/v1/action/')
        action_url = reverse('recipes:action-implementation', kwargs={
            'name': action.name,
            'impl_hash': action.implementation_hash,
        })
        assert res.status_code == 200
        assert res.data == [
            {
                'name': 'foo',
                'implementation_url': Whatever.endswith(action_url),
                'arguments_schema': {'type': 'object'}
            }
        ]
Ejemplo n.º 40
0
    def test_it_works(self, rf):
        recipe = RecipeFactory(arguments={'foo': 'bar'})
        action = recipe.action
        serializer = RecipeSerializer(recipe, context={'request': rf.get('/')})

        action_url = reverse('recipes:action-implementation', kwargs={
            'name': action.name,
            'impl_hash': action.implementation_hash,
        })
        assert serializer.data == {
            'name': recipe.name,
            'id': recipe.id,
            'revision_id': recipe.revision_id,
            'action': {
                'name': action.name,
                'implementation_url': Whatever.endswith(action_url),
                'arguments_schema': action.arguments_schema,
            },
            'arguments': {
                'foo': 'bar',
            }
        }
Ejemplo n.º 41
0
    def test_it_serves_recipes(self, client):
        recipe = RecipeFactory(arguments={'foo': 'bar'})
        data = ClientParametersFactory()
        res = client.post('/api/v1/fetch_bundle/', data)

        action = recipe.action
        impl_url = reverse('recipes:action-implementation', kwargs={
            'name': action.name,
            'impl_hash': action.implementation_hash,
        })
        assert res.status_code == 200
        assert res.data['recipes'] == [{
            'name': recipe.name,
            'id': recipe.id,
            'revision_id': recipe.revision_id,
            'action': {
                'name': action.name,
                'implementation_url': Whatever.endswith(impl_url),
                'arguments_schema': action.arguments_schema,
            },
            'arguments': {
                'foo': 'bar',
            },
        }]
Ejemplo n.º 42
0
    def test_it_interacts_with_autograph_correctly(self, settings, mock_logger):
        settings.AUTOGRAPH_URL = "https://autograph.example.com"
        settings.AUTOGRAPH_HAWK_ID = "hawk id"
        settings.AUTOGRAPH_HAWK_SECRET_KEY = "hawk secret key"

        autographer = signing.Autographer()
        autographer.session = MagicMock()

        autographer.session.post.return_value.json.return_value = [
            {
                "content-signature": (
                    'x5u="https://example.com/fake_x5u_1";p384ecdsa=fake_signature_1'
                ),
                "x5u": "https://example.com/fake_x5u_1",
                "hash_algorithm": "sha384",
                "ref": "fake_ref_1",
                "signature": "fake_signature_1",
                "public_key": "fake_pubkey_1",
            },
            {
                "content-signature": (
                    'x5u="https://example.com/fake_x5u_2";p384ecdsa=fake_signature_2'
                ),
                "x5u": "https://example.com/fake_x5u_2",
                "hash_algorithm": "sha384",
                "ref": "fake_ref_2",
                "signature": "fake_signature_2",
                "public_key": "fake_pubkey_2",
            },
        ]

        url = self.test_settings["URL"] + "sign/data"
        foo_base64 = base64.b64encode(b"foo").decode("utf8")
        bar_base64 = base64.b64encode(b"bar").decode("utf8")

        # Assert the correct data is returned
        assert autographer.sign_data([b"foo", b"bar"]) == [
            {
                "timestamp": Whatever(),
                "signature": "fake_signature_1",
                "x5u": "https://example.com/fake_x5u_1",
                "public_key": "fake_pubkey_1",
            },
            {
                "timestamp": Whatever(),
                "signature": "fake_signature_2",
                "x5u": "https://example.com/fake_x5u_2",
                "public_key": "fake_pubkey_2",
            },
        ]

        # Assert that logging happened
        mock_logger.info.assert_called_with(
            Whatever.contains("2"), extra={"code": signing.INFO_RECEIVED_SIGNATURES}
        )

        # Assert the correct request was made
        assert autographer.session.post.called_once_with(
            [
                url,
                [
                    {"template": "content-signature", "input": foo_base64},
                    {"template": "content-signature", "input": bar_base64},
                ],
            ]
        )
Ejemplo n.º 43
0
 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}
     )