def test_clean_condition_empty_column_is_none(self): self.assertIsNone( self._call_clean_value( ParamSchema.Condition(), { "operation": "text_is", "column": "", "value": "", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], )) # And test it in the context of a broader and/or self.assertIsNone( self._call_clean_value( ParamSchema.Condition(), { "operation": "and", "conditions": [{ "operation": "or", "conditions": [{ "operation": "text_is", "column": "", "value": "", "isCaseSensitive": False, "isRegex": False, }], }], }, input_table_columns=[NUMBER("A")], ))
def test_clean_condition_and_or_simplify(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "and", "conditions": [ { "operation": "or", "conditions": [ { "operation": "cell_is_blank", "column": "A", "value": "", "isCaseSensitive": False, "isRegex": False, }, ], }, ], }, input_table_columns=[NUMBER("A")], ), { "operation": "cell_is_blank", "column": "A", }, )
def test_validate_not_2_levels(self): comparison = { "operation": "text_is", "column": "A", "value": "x", "isCaseSensitive": True, "isRegex": False, } # level 0 with pytest.raises(ValueError): S.Condition().validate(comparison) # level 1 with pytest.raises(ValueError): S.Condition().validate({"operation": "and", "conditions": [comparison]}) # level 2 is okay S.Condition().validate( { "operation": "and", "conditions": [{"operation": "or", "conditions": [comparison]}], } ) # level 3 with pytest.raises(ValueError): S.Condition().validate( { "operation": "and", "conditions": [ { "operation": "or", "conditions": [ {"operation": "and", "conditions": [comparison]} ], } ], } )
def test_clean_condition_missing_column_is_none(self): self.assertIsNone( self._call_clean_value( ParamSchema.Condition(), { "operation": "text_is", "column": "B", "value": "", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], ))
def test_validate_missing_key(self): comparison = { "operation": "text_is", "column": "A", "value": "x", "isCaseSensitive": True, } with pytest.raises(ValueError): S.Condition().validate( { "operation": "and", "conditions": [{"operation": "or", "conditions": [comparison]}], } )
def test_clean_condition_empty_and_and_or_are_none(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "and", "conditions": [{ "operation": "or", "conditions": [] }], }, input_table_columns=[NUMBER("A")], ), None, )
def test_clean_condition_number_wrong_value(self): with self.assertRaises(PromptingError) as cm: self._call_clean_value( ParamSchema.Condition(), { "operation": "number_is", "column": "A", "value": "bad", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], ) self.assertEqual(cm.exception.errors, [PromptingError.CannotCoerceValueToNumber("bad")])
def test_validate_empty_operation_is_okay(self): # The UI lets users select nothing. We can't stop them. comparison = { "operation": "", "column": "A", "value": "x", "isCaseSensitive": True, "isRegex": False, } S.Condition().validate( { "operation": "and", "conditions": [{"operation": "or", "conditions": [comparison]}], } )
def test_validate_condition_value_wrong_type(self): comparison = { "operation": "text_is", "column": "A", "value": 312, "isCaseSensitive": True, "isRegex": False, } with pytest.raises(ValueError): S.Condition().validate( { "operation": "and", "conditions": [{"operation": "or", "conditions": [comparison]}], } )
def test_validate_no_such_operation(self): comparison = { "operation": "text_is_blargy", "column": "A", "value": "x", "isCaseSensitive": True, "isRegex": False, } with pytest.raises(ValueError): S.Condition().validate( { "operation": "and", "conditions": [{"operation": "or", "conditions": [comparison]}], } )
def test_clean_condition_not_with_subclause_error(self): with self.assertRaises(PromptingError) as cm: self._call_clean_value( ParamSchema.Condition(), { "operation": "text_is", "column": "A", "value": "", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], ) self.assertEqual( cm.exception.errors, [PromptingError.WrongColumnType(["A"], None, frozenset({"text"}))], )
def test_clean_condition_untyped(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "cell_is_blank", "column": "A", "value": "2020-11-01", "isCaseSensitive": True, "isRegex": False, }, input_table_columns=[NUMBER("A")], ), { "operation": "cell_is_blank", "column": "A", }, )
def test_clean_condition_timestamp_happy_path(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "timestamp_is_greater_than", "column": "A", "value": "2020-11-01", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[TIMESTAMP("A")], ), { "operation": "timestamp_is_greater_than", "column": "A", "value": "2020-11-01", }, )
def test_clean_condition_number_happy_path(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "number_is", "column": "A", "value": "1", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], ), { "operation": "number_is", "column": "A", "value": 1, }, )
def test_clean_condition_timestamp_wrong_value(self): with self.assertRaises(PromptingError) as cm: self._call_clean_value( ParamSchema.Condition(), { "operation": "timestamp_is_greater_than", "column": "A", "value": "Yesterday", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[TIMESTAMP("A")], ) self.assertEqual( cm.exception.errors, [ PromptingError.CannotCoerceValueToTimestamp("Yesterday"), ], )
def test_clean_condition_text_happy_path(self): self.assertEqual( self._call_clean_value( ParamSchema.Condition(), { "operation": "text_is", "column": "A", "value": "a", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[TEXT("A")], ), { "operation": "text_is", "column": "A", "value": "a", "isCaseSensitive": False, "isRegex": False, }, )
def test_clean_condition_number_wrong_column_type(self): with self.assertRaises(PromptingError) as cm: self._call_clean_value( ParamSchema.Condition(), { "operation": "number_is", "column": "A", "value": "1", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[TEXT("A")], ) self.assertEqual( cm.exception.errors, [ PromptingError.WrongColumnType(["A"], "text", frozenset({"number"})) ], )
def test_clean_condition_timestamp_wrong_column_type(self): with self.assertRaises(PromptingError) as cm: self._call_clean_value( ParamSchema.Condition(), { "operation": "timestamp_is_greater_than", "column": "A", "value": "2020-01-01T00:00Z", "isCaseSensitive": False, "isRegex": False, }, input_table_columns=[NUMBER("A")], ) self.assertEqual( cm.exception.errors, [ PromptingError.WrongColumnType( ["A"], "number", frozenset({"date", "timestamp"})), ], )
def test_param_schema_includes_empty_tuples(): # Bug on 2021-04-21: empty NamedTuple ParamSchema classes evaluate to # False; but they should still be included in the param_schema. spec = load_spec( dict( id_name="x", name="x", category="Clean", parameters=[ dict(id_name="timezone", name="timezone", type="timezone"), dict(id_name="tab", name="tab", type="tab"), dict(id_name="condition", type="condition"), ], ) ) assert spec.param_schema == ParamSchema.Dict( { "timezone": ParamSchema.Timezone(), "tab": ParamSchema.Tab(), "condition": ParamSchema.Condition(), } )
def test_default(self): assert S.Condition().default == {"operation": "and", "conditions": []}
def test_validate_non_dict(self): with pytest.raises(ValueError): S.Condition().validate([])
def test_validate_missing_conditions(self): with pytest.raises(ValueError): S.Condition().validate({"operation": "and", "condition": []})
def test_validate_conditions_not_list(self): with pytest.raises(ValueError): S.Condition().validate({"operation": "and", "conditions": "hi"})
def test_validate_and_with_extra_property(self): with pytest.raises(ValueError): S.Condition().validate({"operation": "and", "conditions": [], "foo": "bar"})