"""Step definitions for AI models providers unit tests.""" import json from behave import given, then, when from pydantic import ValidationError from cleveragents.domain.models.aimodelsproviders.ai_models_providers import ( ModelProviderConfigSchema, ModelProviderExtraAuthVars, ) from cleveragents.domain.models.core.enums import ModelProvider @given("I import the ModelProviderExtraAuthVars class") def step_import_extra_auth_vars(context): """Import ModelProviderExtraAuthVars class.""" context.model_class = ModelProviderExtraAuthVars @given("I import the ModelProviderConfigSchema class") def step_import_config_schema(context): """Import ModelProviderConfigSchema class.""" context.model_class = ModelProviderConfigSchema @given("I import the ModelProvider enum for provider config") def step_import_model_provider_enum_for_config(context): """Import ModelProvider enum for provider config.""" context.enum_class = ModelProvider @when('I create a ModelProviderExtraAuthVars with var "{var}"') def step_create_extra_auth_vars_with_var(context, var): """Create ModelProviderExtraAuthVars with var field.""" context.instance = ModelProviderExtraAuthVars(var=var) @when("I set maybeJSONFilePath to {value}") def step_set_maybe_json_file_path(context, value): """Set maybeJSONFilePath field.""" value_bool = value.lower() == "true" context.instance = ModelProviderExtraAuthVars( var=context.instance.var, maybe_j_s_o_n_file_path=value_bool ) @when("I set required to {value}") def step_set_required(context, value): """Set required field.""" value_bool = value.lower() == "true" context.instance = ModelProviderExtraAuthVars( var=context.instance.var, maybe_j_s_o_n_file_path=context.instance.maybe_j_s_o_n_file_path, required=value_bool, ) @when('I set default to "{value}"') def step_set_default(context, value): """Set default field.""" context.instance = ModelProviderExtraAuthVars( var=context.instance.var, maybe_j_s_o_n_file_path=context.instance.maybe_j_s_o_n_file_path, required=context.instance.required, default=value, ) @when('I create a ModelProviderExtraAuthVars with only var "{var}"') def step_create_minimal_extra_auth_vars(context, var): """Create ModelProviderExtraAuthVars with only required field.""" context.instance = ModelProviderExtraAuthVars(var=var) @when('I create a ModelProviderExtraAuthVars using alias "{alias}"') def step_create_with_alias(context, alias): """Create ModelProviderExtraAuthVars using field alias.""" context.instance = ModelProviderExtraAuthVars( var="TEST_VAR", maybeJSONFilePath=True, # Using alias ) @when('I create a ModelProviderConfigSchema with provider "{provider}"') def step_create_config_schema_with_provider(context, provider): """Create ModelProviderConfigSchema with provider.""" context.instance = ModelProviderConfigSchema( provider=provider, base_url="https://api.example.com" ) @when('I set base_url to "{url}"') def step_set_base_url(context, url): """Set base_url field.""" context.instance.base_url = url @when('I set custom_provider to "{value}"') def step_set_custom_provider(context, value): """Set custom_provider field.""" context.instance.custom_provider = value @when("I set has_a_w_s_auth to {value}") def step_set_has_aws_auth(context, value): """Set has_a_w_s_auth field.""" context.instance.has_a_w_s_auth = value.lower() == "true" @when("I set has_claude_max_auth to {value}") def step_set_has_claude_max_auth(context, value): """Set has_claude_max_auth field.""" context.instance.has_claude_max_auth = value.lower() == "true" @when("I set skip_auth to {value}") def step_set_skip_auth(context, value): """Set skip_auth field.""" context.instance.skip_auth = value.lower() == "true" @when("I set local_only to {value}") def step_set_local_only(context, value): """Set local_only field.""" context.instance.local_only = value.lower() == "true" @when('I set api_key_env_var to "{value}"') def step_set_api_key_env_var(context, value): """Set api_key_env_var field.""" context.instance.api_key_env_var = value @when("I add extra_auth_vars list") def step_add_extra_auth_vars_list(context): """Add extra_auth_vars list.""" extra_var = ModelProviderExtraAuthVars(var="EXTRA_KEY", required=True) context.instance.extra_auth_vars = [extra_var] @when("I create a ModelProviderConfigSchema with only required fields") def step_create_minimal_config_schema(context): """Create ModelProviderConfigSchema with only required fields.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com" ) @when("I create a ModelProviderConfigSchema with ModelProvider.ANTHROPIC") def step_create_config_with_enum(context): """Create ModelProviderConfigSchema with enum value.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.ANTHROPIC, base_url="https://api.anthropic.com" ) @when("I create a ModelProviderConfigSchema with whitespace in fields") def step_create_config_with_whitespace(context): """Create ModelProviderConfigSchema with whitespace in fields.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url=" https://api.openai.com ", custom_provider=" custom ", api_key_env_var=" API_KEY ", ) @when("I create a ModelProviderConfigSchema using aliases") def step_create_config_using_aliases(context): """Create ModelProviderConfigSchema using field aliases.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, baseUrl="https://api.openai.com", # alias customProvider="custom", # alias hasAWSAuth=True, # alias hasClaudeMaxAuth=False, # alias skipAuth=False, # alias localOnly=False, # alias apiKeyEnvVar="API_KEY", # alias extraAuthVars=[], # alias ) @when("I create a ModelProviderConfigSchema with empty extra_auth_vars list") def step_create_config_with_empty_list(context): """Create ModelProviderConfigSchema with empty extra_auth_vars.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com", extra_auth_vars=[], ) @when("I create a ModelProviderConfigSchema with multiple extra_auth_vars") def step_create_config_with_multiple_auth_vars(context): """Create ModelProviderConfigSchema with multiple extra_auth_vars.""" vars_list = [ ModelProviderExtraAuthVars(var="VAR1", required=True), ModelProviderExtraAuthVars(var="VAR2", required=False), ModelProviderExtraAuthVars(var="VAR3", default="default_val"), ] context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com", extra_auth_vars=vars_list, ) @when("I export it to dict with aliases") def step_export_to_dict_with_aliases(context): """Export model to dictionary with aliases.""" context.dict_output = context.instance.model_dump(by_alias=True) @when("I serialize it to JSON") def step_serialize_to_json(context): """Serialize model to JSON.""" context.json_output = context.instance.model_dump_json(by_alias=True) @when("I deserialize it to ModelProviderConfigSchema") def step_deserialize_from_json(context): """Deserialize JSON to ModelProviderConfigSchema.""" json_data = { "provider": "ModelProviderOpenAI", "baseUrl": "https://api.openai.com", "customProvider": "custom", "hasAWSAuth": True, "extraAuthVars": [{"var": "KEY1", "required": True}], } context.json_string = json.dumps(json_data) context.instance = ModelProviderConfigSchema.model_validate_json( context.json_string ) @when("I check the model configuration") def step_check_model_config(context): """Check model configuration.""" context.model_config = context.model_class.model_config @when('I update the var field to "{value}"') def step_update_var_field(context, value): """Update var field.""" context.instance.var = value @when('I update the base_url field to "{value}"') def step_update_base_url_field(context, value): """Update base_url field.""" context.instance.base_url = value @when("I create a copy with updated fields") def step_create_copy_with_updates(context): """Create a copy with updated fields.""" context.original = context.instance if hasattr(context.instance, "var"): context.copy = context.instance.model_copy(update={"var": "NEW_VAR"}) else: context.copy = context.instance.model_copy( update={"base_url": "https://new.com"} ) @when("I compare them for equality") def step_compare_equality(context): """Compare instances for equality.""" if ( hasattr(context, "model_class") and context.model_class == ModelProviderExtraAuthVars ): context.instance1 = ModelProviderExtraAuthVars(var="VAR1") context.instance2 = ModelProviderExtraAuthVars(var="VAR1") context.instance3 = ModelProviderExtraAuthVars(var="VAR2") else: context.instance1 = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com" ) context.instance2 = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com" ) context.instance3 = ModelProviderConfigSchema( provider=ModelProvider.ANTHROPIC, base_url="https://api.anthropic.com" ) @when("I try to use them as dictionary keys") def step_try_use_as_dict_keys(context): """Try to use instances as dictionary keys.""" context.instance1 = ModelProviderExtraAuthVars(var="KEY1") context.instance2 = ModelProviderExtraAuthVars(var="KEY2") context.test_dict = {} try: context.test_dict[context.instance1] = "value1" context.test_dict[context.instance2] = "value2" context.can_be_key = True context.hash_error = None except TypeError as e: context.can_be_key = False context.hash_error = e @when("I try to create an instance with missing required fields") def step_create_with_missing_fields(context): """Try to create instance with missing required fields.""" try: context.instance = ModelProviderConfigSchema() context.error_raised = False except ValidationError as e: context.error = e context.error_raised = True @then('the var field should equal "{expected}"') def step_verify_var_field(context, expected): """Verify var field value.""" assert context.instance.var == expected @then("the maybe_j_s_o_n_file_path field should be {expected}") def step_verify_maybe_json_field(context, expected): """Verify maybe_j_s_o_n_file_path field.""" if expected == "None": assert context.instance.maybe_j_s_o_n_file_path is None else: assert context.instance.maybe_j_s_o_n_file_path == (expected.lower() == "true") @then("the required field should be {expected}") def step_verify_required_field(context, expected): """Verify required field.""" if expected == "None": assert context.instance.required is None else: assert context.instance.required == (expected.lower() == "true") @then('the default field should equal "{expected}"') def step_verify_default_field_string(context, expected): """Verify default field with string value.""" assert context.instance.default == expected @then("the default field should be {expected}") def step_verify_default_field(context, expected): """Verify default field.""" if expected == "None": assert context.instance.default is None else: assert context.instance.default == expected @then("the model should strip whitespace from string fields") def step_verify_whitespace_stripping(context): """Verify whitespace stripping.""" assert context.instance.var == "TRIMMED_VAR" @then("the field should be accessible as maybe_j_s_o_n_file_path") def step_verify_field_accessible(context): """Verify field is accessible with snake_case name.""" assert context.instance.maybe_j_s_o_n_file_path is True @then("the model should populate by name") def step_verify_populate_by_name(context): """Verify model populates by name.""" assert context.instance.maybe_j_s_o_n_file_path is True @then('the dict should contain "{key}" key') def step_verify_dict_contains_key(context, key): """Verify dictionary contains specific key.""" assert key in context.dict_output @then('the dict should not contain "{key}" key') def step_verify_dict_not_contains_key(context, key): """Verify dictionary does not contain specific key.""" assert key not in context.dict_output @then('the provider field should equal "{expected}"') def step_verify_provider_field(context, expected): """Verify provider field value.""" assert context.instance.provider == expected @then('the base_url field should equal "{expected}"') def step_verify_base_url_field(context, expected): """Verify base_url field value.""" assert context.instance.base_url == expected @then('the custom_provider field should equal "{expected}"') def step_verify_custom_provider_field(context, expected): """Verify custom_provider field value.""" assert context.instance.custom_provider == expected @then("the has_a_w_s_auth field should be {expected}") def step_verify_has_aws_auth_field(context, expected): """Verify has_a_w_s_auth field.""" assert context.instance.has_a_w_s_auth == (expected.lower() == "true") @then("the has_claude_max_auth field should be {expected}") def step_verify_has_claude_max_auth_field(context, expected): """Verify has_claude_max_auth field.""" assert context.instance.has_claude_max_auth == (expected.lower() == "true") @then("the skip_auth field should be {expected}") def step_verify_skip_auth_field(context, expected): """Verify skip_auth field.""" assert context.instance.skip_auth == (expected.lower() == "true") @then("the local_only field should be {expected}") def step_verify_local_only_field(context, expected): """Verify local_only field.""" assert context.instance.local_only == (expected.lower() == "true") @then('the api_key_env_var field should equal "{expected}"') def step_verify_api_key_env_var_field(context, expected): """Verify api_key_env_var field value.""" assert context.instance.api_key_env_var == expected @then("the extra_auth_vars should be a list") def step_verify_extra_auth_vars_is_list(context): """Verify extra_auth_vars is a list.""" assert isinstance(context.instance.extra_auth_vars, list) @then("the provider field should be set") def step_verify_provider_set(context): """Verify provider field is set.""" assert context.instance.provider is not None @then("the base_url field should be set") def step_verify_base_url_set(context): """Verify base_url field is set.""" assert context.instance.base_url is not None @then("all optional fields should be None") def step_verify_optional_fields_none(context): """Verify all optional fields are None.""" assert context.instance.custom_provider is None assert context.instance.has_a_w_s_auth is None assert context.instance.has_claude_max_auth is None assert context.instance.skip_auth is None assert context.instance.local_only is None assert context.instance.api_key_env_var is None assert context.instance.extra_auth_vars is None @then("the model should use enum values") def step_verify_enum_values(context): """Verify model uses enum values.""" assert context.instance.provider == "ModelProviderAnthropic" @then("all string fields should have whitespace stripped") def step_verify_all_whitespace_stripped(context): """Verify all string fields have whitespace stripped.""" assert context.instance.base_url == "https://api.openai.com" assert context.instance.custom_provider == "custom" assert context.instance.api_key_env_var == "API_KEY" @then("the model should validate assignment") def step_verify_validate_assignment(context): """Verify model validates assignment.""" # Assignment validation happens automatically in Pydantic assert True @then('"{alias}" should map to {field}') def step_verify_alias_mapping(context, alias, field): """Verify alias maps to field.""" assert hasattr(context.instance, field.replace('"', "")) @then("the dict should use camelCase keys") def step_verify_camelcase_keys(context): """Verify dictionary uses camelCase keys.""" assert "baseUrl" in context.dict_output @then('"{key}" should be in the dict') def step_verify_key_in_dict(context, key): """Verify key is in dictionary.""" assert key in context.dict_output @then("the extra_auth_vars should be an empty list") def step_verify_empty_list(context): """Verify extra_auth_vars is empty list.""" assert context.instance.extra_auth_vars == [] @then("the model should accept empty lists") def step_verify_accepts_empty_lists(context): """Verify model accepts empty lists.""" assert context.instance.extra_auth_vars == [] @then("each extra_auth_var should be a ModelProviderExtraAuthVars instance") def step_verify_auth_vars_instances(context): """Verify each item is ModelProviderExtraAuthVars instance.""" for item in context.instance.extra_auth_vars: assert isinstance(item, ModelProviderExtraAuthVars) @then("the list should maintain order") def step_verify_list_order(context): """Verify list maintains order.""" assert context.instance.extra_auth_vars[0].var == "VAR1" assert context.instance.extra_auth_vars[1].var == "VAR2" assert context.instance.extra_auth_vars[2].var == "VAR3" @then("the JSON should be valid") def step_verify_valid_json(context): """Verify JSON is valid.""" parsed = json.loads(context.json_output) assert parsed is not None @then("it should contain all set fields") def step_verify_json_contains_fields(context): """Verify JSON contains all set fields.""" parsed = json.loads(context.json_output) assert "provider" in parsed assert "baseUrl" in parsed @then("it should use aliases in the output") def step_verify_json_uses_aliases(context): """Verify JSON uses aliases.""" parsed = json.loads(context.json_output) assert "baseUrl" in parsed assert "base_url" not in parsed @given("I have a JSON string with provider config") def step_have_json_string(context): """Have a JSON string with provider config.""" # JSON string is created in the when step pass @then("the object should be correctly populated") def step_verify_object_populated(context): """Verify object is correctly populated.""" assert context.instance.provider == "ModelProviderOpenAI" assert context.instance.base_url == "https://api.openai.com" assert context.instance.custom_provider == "custom" assert context.instance.has_a_w_s_auth is True @then("aliases should be resolved to field names") def step_verify_aliases_resolved(context): """Verify aliases are resolved to field names.""" assert hasattr(context.instance, "base_url") assert hasattr(context.instance, "has_a_w_s_auth") assert not hasattr(context.instance, "baseUrl") assert not hasattr(context.instance, "hasAWSAuth") @then("str_strip_whitespace should be True") def step_verify_strip_whitespace_config(context): """Verify str_strip_whitespace configuration.""" assert context.model_config.get("str_strip_whitespace") is True @then("validate_assignment should be True") def step_verify_validate_assignment_config(context): """Verify validate_assignment configuration.""" assert context.model_config.get("validate_assignment") is True @then("arbitrary_types_allowed should be False") def step_verify_arbitrary_types_config(context): """Verify arbitrary_types_allowed configuration.""" assert context.model_config.get("arbitrary_types_allowed") is False @then("populate_by_name should be True") def step_verify_populate_by_name_config(context): """Verify populate_by_name configuration.""" assert context.model_config.get("populate_by_name") is True @then("use_enum_values should be True") def step_verify_use_enum_values_config(context): """Verify use_enum_values configuration.""" assert context.model_config.get("use_enum_values") is True @then("the update should be validated") def step_verify_update_validated(context): """Verify update is validated.""" # Validation happens automatically in Pydantic assert True @given("I have a ModelProviderExtraAuthVars instance") def step_have_extra_auth_vars_instance(context): """Have a ModelProviderExtraAuthVars instance.""" context.instance = ModelProviderExtraAuthVars(var="TEST_VAR", required=True) @given("I have a ModelProviderConfigSchema instance") def step_have_config_schema_instance(context): """Have a ModelProviderConfigSchema instance.""" context.instance = ModelProviderConfigSchema( provider=ModelProvider.OPENAI, base_url="https://api.openai.com", custom_provider="custom", ) @then("the copy should have new values") def step_verify_copy_new_values(context): """Verify copy has new values.""" if hasattr(context.copy, "var"): assert context.copy.var == "NEW_VAR" else: assert context.copy.base_url == "https://new.com" @then("the original should remain unchanged") def step_verify_original_unchanged(context): """Verify original remains unchanged.""" if hasattr(context.original, "var"): assert context.original.var != "NEW_VAR" else: assert context.original.base_url != "https://new.com" @given("I have two ModelProviderExtraAuthVars instances") def step_have_two_extra_auth_vars(context): """Have two ModelProviderExtraAuthVars instances.""" context.model_class = ModelProviderExtraAuthVars @given("I have two ModelProviderConfigSchema instances") def step_have_two_config_schemas(context): """Have two ModelProviderConfigSchema instances.""" context.model_class = ModelProviderConfigSchema @then("identical instances should be equal") def step_verify_identical_equal(context): """Verify identical instances are equal.""" assert context.instance1 == context.instance2 @then("different instances should not be equal") def step_verify_different_not_equal(context): """Verify different instances are not equal.""" assert context.instance1 != context.instance3 @given("I have ModelProviderExtraAuthVars instances") def step_have_extra_auth_vars_instances(context): """Have ModelProviderExtraAuthVars instances.""" pass # Instances created in when step @then("the instances should not work as dictionary keys") def step_verify_dict_keys_dont_work(context): """Verify instances don't work as dictionary keys.""" assert context.can_be_key is False assert context.hash_error is not None @then("a TypeError should be raised when hashing") def step_verify_type_error_hashing(context): """Verify TypeError is raised when hashing.""" assert isinstance(context.hash_error, TypeError) assert "unhashable" in str(context.hash_error) @then("a validation error should be raised for missing fields") def step_verify_validation_error_raised_missing(context): """Verify validation error is raised for missing fields.""" assert context.error_raised is True @then("the error should indicate missing fields") def step_verify_error_indicates_missing(context): """Verify error indicates missing fields.""" error_str = str(context.error) assert "provider" in error_str.lower() or "base_url" in error_str.lower()