diff --git a/lib/dashbot/features/chat/models/chat_response.dart b/lib/dashbot/features/chat/models/chat_response.dart index 7036a5f5..8625537a 100644 --- a/lib/dashbot/features/chat/models/chat_response.dart +++ b/lib/dashbot/features/chat/models/chat_response.dart @@ -18,18 +18,4 @@ class ChatResponse { messageType: messageType ?? this.messageType, ); } - - @override - String toString() => - 'ChatResponse(content: $content, messageType: $messageType)'; - - @override - bool operator ==(covariant ChatResponse other) { - if (identical(this, other)) return true; - - return other.content == content && other.messageType == messageType; - } - - @override - int get hashCode => content.hashCode ^ messageType.hashCode; } diff --git a/test/dashbot/models/chat_action_model_test.dart b/test/dashbot/models/chat_action_model_test.dart new file mode 100644 index 00000000..04dbebd4 --- /dev/null +++ b/test/dashbot/models/chat_action_model_test.dart @@ -0,0 +1,45 @@ +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatAction serialization & defaults', () { + test('fromJson (full) -> correct enum mapping', () { + final json = { + 'action': 'apply_openapi', + 'target': 'documentation', + 'field': 'spec', + 'path': '/components/schemas', + 'value': {'k': 'v'}, + }; + final action = ChatAction.fromJson(json); + expect(action.actionType, ChatActionType.applyOpenApi); + expect(action.targetType, ChatActionTarget.documentation); + expect(action.field, 'spec'); + expect(action.path, '/components/schemas'); + expect(action.value, {'k': 'v'}); + }); + + test('fromJson (missing fields) -> uses safe defaults', () { + final action = ChatAction.fromJson({}); + expect(action.actionType, ChatActionType.other); + expect(action.targetType, ChatActionTarget.httpRequestModel); + expect(action.field, ''); + expect(action.path, isNull); + }); + + test('toJson produces expected keys & enum strings', () { + const action = ChatAction( + action: 'download_doc', + target: 'documentation', + actionType: ChatActionType.downloadDoc, + targetType: ChatActionTarget.documentation, + ); + final json = action.toJson(); + expect(json, containsPair('action', 'download_doc')); + expect(json, containsPair('target', 'documentation')); + expect(json, containsPair('action_type', 'download_doc')); + expect(json, containsPair('target_type', 'documentation')); + }); + }); +} diff --git a/test/dashbot/models/chat_attachment_model_test.dart b/test/dashbot/models/chat_attachment_model_test.dart new file mode 100644 index 00000000..196c9722 --- /dev/null +++ b/test/dashbot/models/chat_attachment_model_test.dart @@ -0,0 +1,46 @@ +import 'dart:typed_data'; + +import 'package:apidash/dashbot/core/model/chat_attachment.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatAttachment', () { + test('should create model with provided values', () { + final data = Uint8List.fromList(List.generate(16, (i) => i)); + final attachment = ChatAttachment( + id: 'att-1', + name: 'sample.txt', + mimeType: 'text/plain', + sizeBytes: data.length, + data: data, + createdAt: DateTime.utc(2024, 1, 2, 3, 4, 5), + ); + + expect(attachment.id, 'att-1'); + expect(attachment.name, 'sample.txt'); + expect(attachment.mimeType, 'text/plain'); + expect(attachment.sizeBytes, 16); + expect(attachment.data, data); + expect(attachment.createdAt, DateTime.utc(2024, 1, 2, 3, 4, 5)); + }); + + test('immutability (fields are final)', () { + final data = Uint8List.fromList([1, 2, 3]); + final createdAt = DateTime.now(); + final attachment = ChatAttachment( + id: 'id', + name: 'a.bin', + mimeType: 'application/octet-stream', + sizeBytes: 3, + data: data, + createdAt: createdAt, + ); + + // Ensuring references are the same (no defensive copy inside constructor) + expect(identical(attachment.data, data), true); + + // This test only executes field access to ensure coverage; no setters exist. + expect(() => (attachment.id), returnsNormally); + }); + }); +} diff --git a/test/dashbot/models/chat_failure_model_test.dart b/test/dashbot/models/chat_failure_model_test.dart new file mode 100644 index 00000000..4592292c --- /dev/null +++ b/test/dashbot/models/chat_failure_model_test.dart @@ -0,0 +1,11 @@ +import 'package:apidash/dashbot/core/error/chat_failure.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatFailure basics', () { + test('toString format', () { + const f = ChatFailure('network down', code: '503'); + expect(f.toString(), 'ChatFailure: network down'); + }); + }); +} diff --git a/test/dashbot/models/chat_message_model_test.dart b/test/dashbot/models/chat_message_model_test.dart new file mode 100644 index 00000000..dde69414 --- /dev/null +++ b/test/dashbot/models/chat_message_model_test.dart @@ -0,0 +1,65 @@ +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_message.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatMessage equality & hashCode', () { + final ts = DateTime.utc(2025, 1, 2, 3, 4, 5); + const action = ChatAction( + action: 'update_field', + target: 'httpRequestModel', + field: 'url', + value: 'https://api.example.dev', + actionType: ChatActionType.updateField, + targetType: ChatActionTarget.httpRequestModel, + ); + + test('identical field values -> objects equal & same hashCode', () { + final msg1 = ChatMessage( + id: 'm1', + content: 'Hello', + role: MessageRole.user, + timestamp: ts, + messageType: ChatMessageType.general, + actions: const [action], + ); + final msg2 = ChatMessage( + id: 'm1', + content: 'Hello', + role: MessageRole.user, + timestamp: ts, + messageType: ChatMessageType.general, + actions: const [action], + ); + expect(msg1, msg2); + expect(msg1.hashCode, msg2.hashCode); + expect(msg1.toString(), contains('ChatMessage')); + expect(msg1.toString(), contains('m1')); + }); + + test('different id -> not equal', () { + final a = ChatMessage( + id: 'a', + content: 'Hi', + role: MessageRole.user, + timestamp: ts, + ); + final b = a.copyWith(id: 'b'); + expect(a == b, isFalse); + }); + + test('copyWith returns updated instance only for provided fields', () { + final base = ChatMessage( + id: 'base', + content: 'Original', + role: MessageRole.system, + timestamp: ts, + ); + final updated = base.copyWith(content: 'Updated'); + expect(updated.content, 'Updated'); + expect(updated.id, 'base'); + expect(updated.role, MessageRole.system); + }); + }); +} diff --git a/test/dashbot/models/chat_models_test.dart b/test/dashbot/models/chat_models_test.dart new file mode 100644 index 00000000..5ff61d7c --- /dev/null +++ b/test/dashbot/models/chat_models_test.dart @@ -0,0 +1,144 @@ +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/core/error/chat_failure.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_action.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_message.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_response.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_state.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatMessage & ChatState', () { + test('ChatMessage copyWith preserves and overrides fields', () { + final ts = DateTime.utc(2024, 5, 6, 7, 8, 9); + const action = ChatAction( + action: 'update_field', + target: 'httpRequestModel', + field: 'url', + value: 'https://example.com', + actionType: ChatActionType.updateField, + targetType: ChatActionTarget.httpRequestModel, + ); + + final m1 = ChatMessage( + id: 'm1', + content: 'Hello', + role: MessageRole.user, + timestamp: ts, + messageType: ChatMessageType.general, + actions: const [action], + ); + + final m2 = m1.copyWith(content: 'Hi', role: MessageRole.system); + expect(m2.id, 'm1'); + expect(m2.content, 'Hi'); + expect(m2.role, MessageRole.system); + expect(m2.timestamp, ts); + expect(m2.messageType, ChatMessageType.general); + expect(m2.actions, isNotNull); + expect(m2.actions!.length, 1); + }); + + test('ChatState copyWith', () { + final msg = ChatMessage( + id: '1', + content: 'c', + role: MessageRole.user, + timestamp: DateTime.fromMillisecondsSinceEpoch(0), + ); + const failure = ChatFailure('net down', code: '500'); + const state = ChatState(); + final updated = state.copyWith( + chatSessions: { + 'req1': [msg] + }, + isGenerating: true, + currentStreamingResponse: 'streaming..', + currentRequestId: 'req1', + lastError: failure, + ); + + expect(updated.chatSessions['req1']!.first, msg); + expect(updated.isGenerating, true); + expect(updated.currentStreamingResponse, 'streaming..'); + expect(updated.currentRequestId, 'req1'); + expect(updated.lastError, failure); + + // unchanged original + expect(state.chatSessions, isEmpty); + expect(state.isGenerating, false); + }); + + test('ChatResponse copyWith', () { + const r1 = + ChatResponse(content: 'Hello', messageType: ChatMessageType.general); + final r2 = r1.copyWith(content: 'Hi again'); + expect(r2.content, 'Hi again'); + expect(r2.messageType, ChatMessageType.general); + final r3 = r2.copyWith(messageType: ChatMessageType.generateCode); + expect(r3.messageType, ChatMessageType.generateCode); + expect(r3.content, 'Hi again'); + }); + }); + + group('ChatAction serialization', () { + test('fromJson maps to enums correctly', () { + final json = { + 'action': 'apply_curl', + 'target': 'httpRequestModel', + 'field': 'body', + 'path': '/root', + 'value': '--curl command--', + }; + + final action = ChatAction.fromJson(json); + expect(action.actionType, ChatActionType.applyCurl); + expect(action.targetType, ChatActionTarget.httpRequestModel); + expect(action.field, 'body'); + expect(action.path, '/root'); + expect(action.value, '--curl command--'); + }); + + test('fromJson with unknown values defaults gracefully', () { + final action = ChatAction.fromJson({ + 'action': 'some_new_action', + 'target': 'weird_target', + }); + expect(action.actionType, ChatActionType.other); // unknown -> other + expect(action.targetType, ChatActionTarget.httpRequestModel); // default + expect(action.field, ''); + }); + + test('toJson returns enum string representations', () { + const action = ChatAction( + action: 'download_doc', + target: 'documentation', + field: 'n/a', + actionType: ChatActionType.downloadDoc, + targetType: ChatActionTarget.documentation, + ); + final json = action.toJson(); + expect(json['action'], 'download_doc'); + expect(json['target'], 'documentation'); + expect(json['action_type'], 'download_doc'); + expect(json['target_type'], 'documentation'); + }); + }); + + group('Enum mapping helpers', () { + test('chatActionTypeToString covers all values', () { + for (final t in ChatActionType.values) { + final s = chatActionTypeToString(t); + expect(s, isA()); + expect(s.isNotEmpty, true); + } + }); + + test('chatActionTargetToString covers all values', () { + for (final t in ChatActionTarget.values) { + final s = chatActionTargetToString(t); + expect(s, isA()); + expect(s.isNotEmpty, true); + } + }); + }); +} diff --git a/test/dashbot/models/chat_response_model_test.dart b/test/dashbot/models/chat_response_model_test.dart new file mode 100644 index 00000000..218c189a --- /dev/null +++ b/test/dashbot/models/chat_response_model_test.dart @@ -0,0 +1,27 @@ +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_response.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatResponse equality & copyWith', () { + test('equality & hashCode', () { + const r1 = + ChatResponse(content: 'Hi', messageType: ChatMessageType.general); + const r2 = + ChatResponse(content: 'Hi', messageType: ChatMessageType.general); + expect(r1, r2); + expect(r1.hashCode, r2.hashCode); + expect(r1.toString(), contains('ChatResponse')); + }); + + test('copyWith modifies only provided fields', () { + const r1 = ChatResponse(content: 'One'); + final r2 = r1.copyWith(content: 'Two'); + expect(r2.content, 'Two'); + expect(r2.messageType, isNull); + final r3 = r2.copyWith(messageType: ChatMessageType.generateCode); + expect(r3.messageType, ChatMessageType.generateCode); + expect(r3.content, 'Two'); + }); + }); +} diff --git a/test/dashbot/models/chat_state_model_test.dart b/test/dashbot/models/chat_state_model_test.dart new file mode 100644 index 00000000..ddf2666f --- /dev/null +++ b/test/dashbot/models/chat_state_model_test.dart @@ -0,0 +1,59 @@ +import 'package:apidash/dashbot/core/error/chat_failure.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_message.dart'; +import 'package:apidash/dashbot/features/chat/models/chat_state.dart'; +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:test/test.dart'; + +void main() { + group('ChatState copyWith & integrity', () { + test('copyWith updates provided fields only', () { + final msg = ChatMessage( + id: '1', + content: 'Ping', + role: MessageRole.user, + timestamp: DateTime.fromMillisecondsSinceEpoch(0), + ); + const failure = ChatFailure('generic'); + const base = ChatState(); + final next = base.copyWith( + chatSessions: { + 'r1': [msg] + }, + isGenerating: true, + currentStreamingResponse: 'stream', + currentRequestId: 'r1', + lastError: failure, + ); + expect(next.chatSessions['r1']!.single, msg); + expect(next.isGenerating, true); + expect(next.currentStreamingResponse, 'stream'); + expect(next.currentRequestId, 'r1'); + expect(next.lastError, failure); + // Original untouched + expect(base.chatSessions, isEmpty); + expect(base.isGenerating, false); + + // Calling copyWith with NO arguments hits fallback (right side of ??) + final same = next.copyWith(); + expect(same.chatSessions, next.chatSessions); + expect(same.isGenerating, next.isGenerating); + expect(same.currentStreamingResponse, next.currentStreamingResponse); + expect(same.currentRequestId, next.currentRequestId); + expect(same.lastError, next.lastError); + + // Explicit null parameters should also fall back to existing values + final viaNulls = next.copyWith( + chatSessions: null, + isGenerating: null, + currentStreamingResponse: null, + currentRequestId: null, + lastError: null, + ); + expect(viaNulls.chatSessions, next.chatSessions); + expect(viaNulls.isGenerating, next.isGenerating); + expect(viaNulls.currentStreamingResponse, next.currentStreamingResponse); + expect(viaNulls.currentRequestId, next.currentRequestId); + expect(viaNulls.lastError, next.lastError); + }); + }); +} diff --git a/test/dashbot/models/constants_model_test.dart b/test/dashbot/models/constants_model_test.dart new file mode 100644 index 00000000..816eac56 --- /dev/null +++ b/test/dashbot/models/constants_model_test.dart @@ -0,0 +1,49 @@ +import 'package:apidash/dashbot/core/constants/constants.dart'; +import 'package:test/test.dart'; + +void main() { + group('Constants enum mappings', () { + test('ChatActionType round-trip mapping', () { + final map = { + ChatActionType.updateField: 'update_field', + ChatActionType.addHeader: 'add_header', + ChatActionType.updateHeader: 'update_header', + ChatActionType.deleteHeader: 'delete_header', + ChatActionType.updateBody: 'update_body', + ChatActionType.updateUrl: 'update_url', + ChatActionType.updateMethod: 'update_method', + ChatActionType.showLanguages: 'show_languages', + ChatActionType.applyCurl: 'apply_curl', + ChatActionType.applyOpenApi: 'apply_openapi', + ChatActionType.downloadDoc: 'download_doc', + ChatActionType.other: 'other', + ChatActionType.noAction: 'no_action', + ChatActionType.uploadAsset: 'upload_asset', + }; + map.forEach((enumVal, strVal) { + expect(chatActionTypeToString(enumVal), strVal); + expect(chatActionTypeFromString(strVal), enumVal); + }); + // unknown + expect(chatActionTypeFromString('some_new_type'), ChatActionType.other); + }); + + test('ChatActionTarget round-trip mapping', () { + final map = { + ChatActionTarget.httpRequestModel: 'httpRequestModel', + ChatActionTarget.codegen: 'codegen', + ChatActionTarget.test: 'test', + ChatActionTarget.code: 'code', + ChatActionTarget.attachment: 'attachment', + ChatActionTarget.documentation: 'documentation', + }; + map.forEach((enumVal, strVal) { + expect(chatActionTargetToString(enumVal), strVal); + expect(chatActionTargetFromString(strVal), enumVal); + }); + // unknown maps to default httpRequestModel + expect(chatActionTargetFromString('weird'), + ChatActionTarget.httpRequestModel); + }); + }); +} diff --git a/test/dashbot/models/dashbot_window_model_test.dart b/test/dashbot/models/dashbot_window_model_test.dart new file mode 100644 index 00000000..86fb3624 --- /dev/null +++ b/test/dashbot/models/dashbot_window_model_test.dart @@ -0,0 +1,58 @@ +import 'package:apidash/dashbot/core/model/dashbot_window_model.dart'; +import 'package:test/test.dart'; + +void main() { + group('DashbotWindowModel', () { + test('default values', () { + const model = DashbotWindowModel(); + expect(model.width, 400); + expect(model.height, 515); + expect(model.right, 50); + expect(model.bottom, 100); + expect(model.isActive, false); + expect(model.isPopped, true); + expect(model.isHidden, false); + }); + + test('copyWith changes only specified fields', () { + const original = DashbotWindowModel(); + final updated = original.copyWith( + width: 420, + height: 600, + isActive: true, + isHidden: true, + ); + + expect(updated.width, 420); + expect(updated.height, 600); + expect(updated.isActive, true); + expect(updated.isHidden, true); + + // Unchanged fields retain original values + expect(updated.right, original.right); + expect(updated.bottom, original.bottom); + expect(updated.isPopped, original.isPopped); + + // Original remains unchanged + expect(original.width, 400); + expect(original.height, 515); + expect(original.isActive, false); + expect(original.isHidden, false); + }); + + test('copyWith chaining', () { + const original = DashbotWindowModel(); + final chained = original + .copyWith(width: 410) + .copyWith(right: 80, bottom: 120) + .copyWith(isPopped: false); + + expect(chained.width, 410); + expect(chained.right, 80); + expect(chained.bottom, 120); + expect(chained.isPopped, false); + // untouched + expect(chained.height, original.height); + }); + }); +}