class TestNumberRangeValidator(unittest.TestCase): """ Number range validator uses the data, which is already known as integer """ def setUp(self): self.store = AnswerStore() answer1 = Answer(answer_id="set-minimum", value=10) answer2 = Answer(answer_id="set-maximum", value=20) answer3 = Answer(answer_id="set-maximum-cat", value="cat") self.store.add_or_update(answer1) self.store.add_or_update(answer2) self.store.add_or_update(answer3) def tearDown(self): self.store.clear() def test_too_small_when_min_set_is_invalid(self): validator = NumberRange(minimum=0) mock_form = Mock() mock_field = Mock() mock_field.data = -10 with self.assertRaises(ValidationError) as ite: validator(mock_form, mock_field) self.assertEqual(error_messages["NUMBER_TOO_SMALL"] % {"min": 0}, str(ite.exception)) def test_too_big_when_max_set_is_invalid(self): validator = NumberRange(maximum=9999999999) mock_form = Mock() mock_field = Mock() mock_field.data = 10000000000 with self.assertRaises(ValidationError) as ite: validator(mock_form, mock_field) self.assertEqual( error_messages["NUMBER_TOO_LARGE"] % {"max": format_number(9999999999)}, str(ite.exception), ) def test_within_range(self): validator = NumberRange(minimum=0, maximum=10) mock_form = Mock() mock_field = Mock() mock_field.data = 10 try: validator(mock_form, mock_field) except ValidationError: self.fail("Valid integer raised ValidationError") def test_within_range_at_min(self): validator = NumberRange(minimum=0, maximum=9999999999) mock_form = Mock() mock_field = Mock() mock_field.data = 0 try: validator(mock_form, mock_field) except ValidationError: self.fail("Valid integer raised ValidationError") def test_within_range_at_max(self): validator = NumberRange(minimum=0, maximum=9999999999) mock_form = Mock() mock_field = Mock() mock_field.data = 9999999999 try: validator(mock_form, mock_field) except ValidationError: self.fail("Valid integer raised ValidationError")
class QuestionnaireStore: LATEST_VERSION = 1 def __init__(self, storage: EncryptedQuestionnaireStorage, version: Optional[int] = None): self._storage = storage if version is None: version = self.get_latest_version_number() self.version = version self._metadata: dict[str, Any] = {} # self.metadata is a read-only view over self._metadata self.metadata: Mapping[str, Any] = MappingProxyType(self._metadata) self.response_metadata: Mapping[str, Any] = {} self.list_store = ListStore() self.answer_store = AnswerStore() self.progress_store = ProgressStore() self.submitted_at: Optional[datetime] self.collection_exercise_sid: Optional[str] ( raw_data, self.collection_exercise_sid, version, self.submitted_at, ) = self._storage.get_user_data() if raw_data: self._deserialize(raw_data) if version is not None: self.version = version def get_latest_version_number(self) -> int: return self.LATEST_VERSION def set_metadata(self, to_set: dict[str, Any]) -> QuestionnaireStore: """ Set metadata. This should only be used where absolutely necessary. Metadata should normally be read only. """ self._metadata = to_set self.metadata = MappingProxyType(self._metadata) return self def _deserialize(self, data: str) -> None: json_data = json_loads(data) self.progress_store = ProgressStore(json_data.get("PROGRESS")) self.set_metadata(json_data.get("METADATA", {})) self.answer_store = AnswerStore(json_data.get("ANSWERS")) self.list_store = ListStore.deserialize(json_data.get("LISTS")) self.response_metadata = json_data.get("RESPONSE_METADATA", {}) def serialize(self) -> str: data = { "METADATA": self._metadata, "ANSWERS": list(self.answer_store), "LISTS": self.list_store.serialize(), "PROGRESS": self.progress_store.serialize(), "RESPONSE_METADATA": self.response_metadata, } return json_dumps(data) def delete(self) -> None: self._storage.delete() self._metadata.clear() self.response_metadata = {} self.answer_store.clear() self.progress_store.clear() def save(self) -> None: data = self.serialize() collection_exercise_sid = (self.collection_exercise_sid or self._metadata["collection_exercise_sid"]) response_expires_at = self._metadata.get("response_expires_at") self._storage.save( data=data, collection_exercise_sid=collection_exercise_sid, submitted_at=self.submitted_at, expires_at=parse_iso_8601_datetime(response_expires_at) if response_expires_at else None, )
class QuestionnaireStore: LATEST_VERSION = 1 def __init__(self, storage, version=None): self._storage = storage if version is None: version = self.get_latest_version_number() self.version = version self._metadata = {} # self.metadata is a read-only view over self._metadata self.metadata = MappingProxyType(self._metadata) self.response_metadata = {} self.list_store = ListStore() self.answer_store = AnswerStore() self.progress_store = ProgressStore() raw_data, version = self._storage.get_user_data() if raw_data: self._deserialize(raw_data) if version is not None: self.version = version def get_latest_version_number(self): return self.LATEST_VERSION def set_metadata(self, to_set): """ Set metadata. This should only be used where absolutely necessary. Metadata should normally be read only. """ self._metadata = to_set self.metadata = MappingProxyType(self._metadata) return self def _deserialize(self, data): json_data = json_loads(data) self.progress_store = ProgressStore(json_data.get("PROGRESS")) self.set_metadata(json_data.get("METADATA", {})) self.answer_store = AnswerStore(json_data.get("ANSWERS")) self.list_store = ListStore.deserialize(json_data.get("LISTS")) self.response_metadata = json_data.get("RESPONSE_METADATA", {}) def serialize(self): data = { "METADATA": self._metadata, "ANSWERS": list(self.answer_store), "LISTS": self.list_store.serialize(), "PROGRESS": self.progress_store.serialize(), "RESPONSE_METADATA": self.response_metadata, } return json_dumps(data) def delete(self): self._storage.delete() self._metadata.clear() self.response_metadata = {} self.answer_store.clear() self.progress_store.clear() def save(self): data = self.serialize() self._storage.save(data=data)