diff --git a/packages/genai/lib/consts.dart b/packages/genai/lib/consts.dart index 6921a8c1..27543c9b 100644 --- a/packages/genai/lib/consts.dart +++ b/packages/genai/lib/consts.dart @@ -5,6 +5,6 @@ const kKeyStream = 'stream'; final kAvailableModels = AvailableModels.fromJson(kModelsData); const kModelRemoteUrl = - 'https://raw.githubusercontent.com/foss42/apidash/package/genai/packages/genai/models.json'; + 'https://raw.githubusercontent.com/foss42/apidash/refs/heads/main/packages/genai/models.json'; const kBaseOllamaUrl = 'http://localhost:11434'; diff --git a/packages/genai/pubspec.yaml b/packages/genai/pubspec.yaml index e24465e6..13237382 100644 --- a/packages/genai/pubspec.yaml +++ b/packages/genai/pubspec.yaml @@ -25,4 +25,3 @@ dev_dependencies: freezed: ^2.5.7 json_serializable: ^6.7.1 test: ^1.25.2 - mocktail: ^1.0.0 diff --git a/packages/genai/test/models/model_config_test.dart b/packages/genai/test/models/model_config_test.dart new file mode 100644 index 00000000..3db741d9 --- /dev/null +++ b/packages/genai/test/models/model_config_test.dart @@ -0,0 +1,83 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:genai/models/model_config.dart'; +import 'package:genai/models/model_config_value.dart'; + +void main() { + group('ModelConfig', () { + test('constructor asserts correct type/value', () { + expect( + () => ModelConfig( + id: '1', + name: 'Temperature', + description: 'test', + type: ConfigType.boolean, + value: ConfigBooleanValue(value: true), + ), + returnsNormally, + ); + + expect( + () => ModelConfig( + id: '2', + name: 'Invalid', + description: 'wrong', + type: ConfigType.boolean, + value: ConfigNumericValue(value: 5), + ), + throwsA(isA()), + ); + }); + + test('updateValue returns new instance with updated value', () { + final config = ModelConfig( + id: '1', + name: 'Numeric', + description: 'test', + type: ConfigType.numeric, + value: ConfigNumericValue(value: 10), + ); + + final updated = config.updateValue(ConfigNumericValue(value: 20)); + expect(updated.value.value, 20); + expect(updated.id, config.id); + }); + + test('toJson and fromJson work correctly', () { + final config = ModelConfig( + id: 'temp', + name: 'Temperature', + description: 'test config', + type: ConfigType.numeric, + value: ConfigNumericValue(value: 5), + ); + + final json = config.toJson(); + expect(json['id'], 'temp'); + expect(json['type'], 'numeric'); + + final from = ModelConfig.fromJson(json); + expect(from.id, 'temp'); + expect(from.value is ConfigNumericValue, true); + expect((from.value as ConfigNumericValue).value, 5); + }); + + test('copyWith creates modified copy', () { + final config = ModelConfig( + id: 'slider', + name: 'Slider', + description: 'range', + type: ConfigType.slider, + value: ConfigSliderValue(value: (0, 0.3, 1)), + ); + + final copy = config.copyWith( + name: 'Updated Slider', + value: ConfigSliderValue(value: (0, 0.7, 1)), + ); + + expect(copy.name, 'Updated Slider'); + expect(copy.value.getPayloadValue(), 0.7); + expect(copy.id, 'slider'); // unchanged + }); + }); +} diff --git a/packages/genai/test/models/model_config_value_test.dart b/packages/genai/test/models/model_config_value_test.dart new file mode 100644 index 00000000..be2b2db0 --- /dev/null +++ b/packages/genai/test/models/model_config_value_test.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:genai/models/model_config_value.dart'; + +void main() { + group('ConfigType Enum', () { + test('getConfigTypeEnum returns correct enum', () { + expect(getConfigTypeEnum('boolean'), ConfigType.boolean); + expect(getConfigTypeEnum('slider'), ConfigType.slider); + expect(getConfigTypeEnum('numeric'), ConfigType.numeric); + expect(getConfigTypeEnum('text'), ConfigType.text); + }); + + test('getConfigTypeEnum falls back to text on invalid', () { + expect(getConfigTypeEnum('does_not_exist'), ConfigType.text); + expect(getConfigTypeEnum(null), ConfigType.text); + }); + }); + + group('ConfigBooleanValue', () { + test('serialize and deserialize works', () { + final value = ConfigBooleanValue(value: true); + final serialized = value.serialize(); + expect(serialized, 'true'); + + final deserialized = ConfigBooleanValue.deserialize(serialized); + expect(deserialized.value, true); + }); + }); + + group('ConfigNumericValue', () { + test('serialize and deserialize works', () { + final value = ConfigNumericValue(value: 42); + final serialized = value.serialize(); + expect(serialized, '42'); + + final deserialized = ConfigNumericValue.deserialize('42'); + expect(deserialized.value, 42); + + final nullValue = ConfigNumericValue.deserialize('not_a_number'); + expect(nullValue.value, null); + }); + }); + + group('ConfigSliderValue', () { + test('serialize and deserialize works', () { + final value = ConfigSliderValue(value: (0.0, 0.5, 1.0)); + final serialized = value.serialize(); + expect(serialized, jsonEncode([0.0, 0.5, 1.0])); + + final deserialized = ConfigSliderValue.deserialize(serialized); + expect(deserialized.value, (0.0, 0.5, 1.0)); + }); + + test('getPayloadValue returns middle element', () { + final slider = ConfigSliderValue(value: (0.0, 0.5, 1.0)); + expect(slider.getPayloadValue(), 0.5); + }); + }); + + group('ConfigTextValue', () { + test('serialize and deserialize works', () { + final value = ConfigTextValue(value: 'hello'); + final serialized = value.serialize(); + expect(serialized, 'hello'); + + final deserialized = ConfigTextValue.deserialize('world'); + expect(deserialized.value, 'world'); + }); + }); + + group('checkTypeValue', () { + test('validates correct type/value matches', () { + expect( + checkTypeValue(ConfigType.boolean, ConfigBooleanValue(value: true)), + true, + ); + expect( + checkTypeValue(ConfigType.numeric, ConfigNumericValue(value: 3)), + true, + ); + expect( + checkTypeValue( + ConfigType.slider, + ConfigSliderValue(value: (0, 0.5, 1)), + ), + true, + ); + expect( + checkTypeValue(ConfigType.text, ConfigTextValue(value: 'hi')), + true, + ); + }); + + test('returns false for mismatched type/value', () { + expect( + checkTypeValue(ConfigType.boolean, ConfigNumericValue(value: 1)), + false, + ); + expect( + checkTypeValue(ConfigType.numeric, ConfigTextValue(value: 'x')), + false, + ); + expect( + checkTypeValue(ConfigType.slider, ConfigBooleanValue(value: true)), + false, + ); + }); + }); +} diff --git a/packages/genai/test/utils.dart/ai_request_utils_test.dart b/packages/genai/test/utils.dart/ai_request_utils_test.dart index 5dcf5640..080a3b99 100644 --- a/packages/genai/test/utils.dart/ai_request_utils_test.dart +++ b/packages/genai/test/utils.dart/ai_request_utils_test.dart @@ -7,7 +7,7 @@ import 'package:genai/interface/consts.dart'; import 'package:genai/utils/ai_request_utils.dart'; import 'package:better_networking/better_networking.dart'; -const kTestingAPIKey = "AIzaSyAtmGxNxlbh_MokoDbMjHKDSW-gU6GCMOU"; +const kTestingAPIKey = "XXXXXXXXXXXX"; void main() { group('ai_request_utils', () { diff --git a/packages/genai/test/utils.dart/available_models_test.dart b/packages/genai/test/utils.dart/available_models_test.dart new file mode 100644 index 00000000..c46df7a7 --- /dev/null +++ b/packages/genai/test/utils.dart/available_models_test.dart @@ -0,0 +1,74 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:genai/models/available_models.dart'; +import 'package:genai/interface/interface.dart'; + +void main() { + group('AvailableModels', () { + test('can parse from JSON and back', () { + const jsonString = ''' + { + "version": 1.0, + "model_providers": [ + { + "provider_id": "openai", + "provider_name": "OpenAI", + "source_url": "https://api.openai.com", + "models": [ + {"id": "gpt-4", "name": "GPT-4"} + ] + } + ] + } + '''; + + final models = availableModelsFromJson(jsonString); + expect(models.version, 1.0); + expect(models.modelProviders.length, 1); + expect(models.modelProviders.first.providerName, "OpenAI"); + + final backToJson = availableModelsToJson(models); + expect(backToJson.contains("GPT-4"), true); + }); + + test('map getter returns map of providers', () { + final provider = AIModelProvider( + providerId: ModelAPIProvider.openai, + providerName: "OpenAI", + models: [const Model(id: "gpt-4", name: "GPT-4")], + ); + + final available = AvailableModels( + version: 1.0, + modelProviders: [provider], + ); + + expect(available.map.containsKey(ModelAPIProvider.openai), true); + expect(available.map[ModelAPIProvider.openai]?.providerName, "OpenAI"); + }); + }); + + group('AIModelProvider', () { + test( + 'toAiRequestModel returns default AIRequestModel with model override', + () { + const provider = AIModelProvider( + providerId: ModelAPIProvider.openai, + providerName: "OpenAI", + ); + + const model = const Model(id: "gpt-4", name: "GPT-4"); + final req = provider.toAiRequestModel(model: model); + + expect(req?.model, "gpt-4"); + }, + ); + }); + + group('Model', () { + test('fromJson works', () { + final model = Model.fromJson({"id": "mistral", "name": "Mistral"}); + expect(model.id, "mistral"); + expect(model.name, "Mistral"); + }); + }); +} diff --git a/packages/genai/test/utils.dart/model_manager_test.dart b/packages/genai/test/utils.dart/model_manager_test.dart new file mode 100644 index 00000000..effb8683 --- /dev/null +++ b/packages/genai/test/utils.dart/model_manager_test.dart @@ -0,0 +1,24 @@ +import 'dart:convert'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:genai/models/available_models.dart'; +import 'package:genai/utils/model_manager.dart'; + +void main() { + group('ModelManager', () { + test('fetchModelsFromRemote returns parsed models', () async { + final result = await ModelManager.fetchModelsFromRemote(); + expect(result, isA()); + }); + + test('fetchInstalledOllamaModels parses response', () async { + final body = jsonEncode({ + "models": [ + {"model": "mistral", "name": "Mistral"}, + {"model": "llama2", "name": "LLaMA 2"}, + ], + }); + final result = await ModelManager.fetchInstalledOllamaModels(); + expect(result, isNotNull); + }); + }); +}