Source code for autowisp.tests.test_diagnostic_expressions

"""Tests for stored diagnostic expressions.

Only what is specific to the model lives here.  That it carries ``created``
and ``modified``, and that they are maintained however the row is written,
is covered once for every browser-interface model by ``test_bui_models``.
"""

import os
import sys
import unittest
from pathlib import Path

import django

_browser_interface = Path(__file__).resolve().parents[1] / "browser_interface"
if str(_browser_interface) not in sys.path:
    sys.path.insert(0, str(_browser_interface))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_project.settings")
django.setup()

# pylint: disable=wrong-import-position
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.management import call_command
from django.db.utils import IntegrityError

from diagnostics.expression_data import get_expressions
from diagnostics.models import DiagnosticExpression

# pylint: enable=wrong-import-position


[docs] class DiagnosticExpressionTestCase(unittest.TestCase): """Base migrating the throwaway browser-interface database."""
[docs] @classmethod def setUpClass(cls): assert "autowisp_tests_" in str( settings.DATABASES["default"]["NAME"] ), "refusing to run against a real browser-interface database" call_command("migrate", verbosity=0)
[docs] def setUp(self): # pylint: disable=no-member DiagnosticExpression.objects.all().delete()
[docs] def make(self, name, expression="astrom_residual / diagonal_fov"): """Store one expression.""" # pylint: disable=no-member return DiagnosticExpression.objects.create( name=name, expression=expression )
[docs] class TestNameSpace(DiagnosticExpressionTestCase): """Names have to behave like the diagnostic names they sit beside."""
[docs] def test_name_is_unique(self): """Two expressions cannot share a name. The name is what a selector and a URL carry, and what other expressions reference, so a duplicate would be ambiguous in three places at once. """ self.make("rel_astrom_residual") with self.assertRaises(IntegrityError): self.make("rel_astrom_residual", "bg_center")
[docs] def test_name_must_survive_a_url(self): """Rejected unless it is a slug. Expressions are selected through ``image/<slug:x>/vs/<slug:y>``, so a name outside the slug charset could be stored but never plotted. """ expression = DiagnosticExpression( name="not a slug!", expression="bg_center" ) with self.assertRaises(ValidationError): expression.full_clean()
[docs] def test_a_slug_name_is_accepted(self): """The names the documentation suggests actually validate.""" for name in ("rel_astrom_residual", "bg-relative", "pixel_q999_ratio"): with self.subTest(name=name): DiagnosticExpression( name=name, expression="bg_center" ).full_clean()
[docs] class TestStoredFields(DiagnosticExpressionTestCase): """What the model keeps, and what it deliberately does not check."""
[docs] def test_description_is_optional(self): """Most expressions are self-explanatory from their text.""" self.make("terse").full_clean()
[docs] def test_unknown_names_are_not_rejected_here(self): """The model stores text; resolving names is not its job. An expression may legitimately reference diagnostics the open project has never recorded -- it is then simply not offered there -- so validating against a project database at this level would be wrong. """ self.make("references_nothing_real", "no_such_diagnostic * 2") # pylint: disable=no-member self.assertEqual( DiagnosticExpression.objects.get( name="references_nothing_real" ).expression, "no_such_diagnostic * 2", )
[docs] def test_ordering_is_by_name(self): """The management page lists them alphabetically.""" for name in ("zeta", "alpha", "mu"): self.make(name) # pylint: disable=no-member self.assertEqual( [row.name for row in DiagnosticExpression.objects.all()], ["alpha", "mu", "zeta"], )
[docs] def test_str_is_the_name(self): """What the admin and any error message will show.""" self.assertEqual(str(self.make("readable")), "readable")
[docs] class TestLibraryAccess(DiagnosticExpressionTestCase): """``get_expressions`` -- the whole of tier 3. What it produces is the ``{name: expression}`` dictionary tiers 1 and 2 take as an argument, so these assert the *shape* of that hand-off rather than anything about expressions, which is tested where the rules live. """
[docs] def test_empty_library_is_a_dictionary(self): """Not ``None``: the tiers below iterate it without checking.""" self.assertEqual(get_expressions(), {})
[docs] def test_names_map_to_their_text(self): """The shape tiers 1 and 2 expect, and nothing more.""" self.make("rel_bg", "bg_center - nanmedian(bg_center)") self.make("twice_bg", "bg_center * 2") self.assertEqual( get_expressions(), { "rel_bg": "bg_center - nanmedian(bg_center)", "twice_bg": "bg_center * 2", }, )
[docs] def test_the_whole_library_regardless_of_what_resolves(self): """Filtering by project would need a project, which tier 3 lacks. An expression naming a diagnostic nothing has recorded is not an error; it is simply not offered where it cannot be drawn, and deciding that belongs to whoever holds the project's names. """ self.make("references_nothing_real", "no_such_diagnostic * 2") self.assertIn("references_nothing_real", get_expressions())
if __name__ == "__main__": unittest.main()