test: add unit tests for Control Room data models
Add comprehensive test coverage for: - ProxyHostModel: 37 tests for JSON converters and serialization - ContainerModel: 44 tests for deserialization, entity conversion, state parsing - StackModel: 55 tests for type/status parsing, timestamps, entity properties 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.5
parent
50f643dd90
commit
67d2f777c8
@@ -0,0 +1,395 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:tatlock_ui/features/control_room/stacks/data/models/stack_model.dart';
|
||||
import 'package:tatlock_ui/features/control_room/stacks/domain/entities/stack.dart';
|
||||
|
||||
void main() {
|
||||
group('StackModel', () {
|
||||
group('fromJson', () {
|
||||
test('deserializes minimal stack', () {
|
||||
final json = {
|
||||
'id': 'stack-123',
|
||||
'name': 'my-stack',
|
||||
};
|
||||
|
||||
final model = StackModel.fromJson(json);
|
||||
|
||||
expect(model.id, equals('stack-123'));
|
||||
expect(model.name, equals('my-stack'));
|
||||
expect(model.containerCount, equals(0)); // Default
|
||||
expect(model.runningCount, equals(0)); // Default
|
||||
});
|
||||
|
||||
test('deserializes full stack with all fields', () {
|
||||
final json = {
|
||||
'id': 'stack-456',
|
||||
'name': 'production-stack',
|
||||
'type': 'compose',
|
||||
'status': 'active',
|
||||
'container_count': 5,
|
||||
'running_count': 5,
|
||||
'compose_file': '/path/to/docker-compose.yml',
|
||||
'environment': 'production',
|
||||
'created_at': '2024-01-15T10:30:00Z',
|
||||
'updated_at': '2024-01-20T15:45:00Z',
|
||||
};
|
||||
|
||||
final model = StackModel.fromJson(json);
|
||||
|
||||
expect(model.id, equals('stack-456'));
|
||||
expect(model.name, equals('production-stack'));
|
||||
expect(model.typeString, equals('compose'));
|
||||
expect(model.statusString, equals('active'));
|
||||
expect(model.containerCount, equals(5));
|
||||
expect(model.runningCount, equals(5));
|
||||
expect(model.composeFile, equals('/path/to/docker-compose.yml'));
|
||||
expect(model.environment, equals('production'));
|
||||
expect(model.createdAt, equals('2024-01-15T10:30:00Z'));
|
||||
expect(model.updatedAt, equals('2024-01-20T15:45:00Z'));
|
||||
});
|
||||
|
||||
test('handles null optional fields', () {
|
||||
final json = {
|
||||
'id': 'stack-789',
|
||||
'name': 'minimal-stack',
|
||||
'type': null,
|
||||
'status': null,
|
||||
'compose_file': null,
|
||||
'environment': null,
|
||||
'created_at': null,
|
||||
'updated_at': null,
|
||||
};
|
||||
|
||||
final model = StackModel.fromJson(json);
|
||||
|
||||
expect(model.typeString, isNull);
|
||||
expect(model.statusString, isNull);
|
||||
expect(model.composeFile, isNull);
|
||||
expect(model.environment, isNull);
|
||||
expect(model.createdAt, isNull);
|
||||
expect(model.updatedAt, isNull);
|
||||
});
|
||||
|
||||
test('uses defaults for missing container counts', () {
|
||||
final json = {
|
||||
'id': 'stack-abc',
|
||||
'name': 'test-stack',
|
||||
};
|
||||
|
||||
final model = StackModel.fromJson(json);
|
||||
|
||||
expect(model.containerCount, equals(0));
|
||||
expect(model.runningCount, equals(0));
|
||||
});
|
||||
});
|
||||
|
||||
group('toEntity', () {
|
||||
test('converts to entity with all fields', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'my-stack',
|
||||
typeString: 'compose',
|
||||
statusString: 'active',
|
||||
containerCount: 3,
|
||||
runningCount: 2,
|
||||
composeFile: '/compose.yml',
|
||||
environment: 'staging',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-02T12:00:00Z',
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.id, equals('stack-123'));
|
||||
expect(entity.name, equals('my-stack'));
|
||||
expect(entity.type, equals(StackType.compose));
|
||||
expect(entity.status, equals(StackStatus.active));
|
||||
expect(entity.containerCount, equals(3));
|
||||
expect(entity.runningCount, equals(2));
|
||||
expect(entity.composeFile, equals('/compose.yml'));
|
||||
expect(entity.environment, equals('staging'));
|
||||
expect(entity.createdAt, isNotNull);
|
||||
expect(entity.updatedAt, isNotNull);
|
||||
});
|
||||
|
||||
test('handles null timestamps', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'my-stack',
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.createdAt, isNull);
|
||||
expect(entity.updatedAt, isNull);
|
||||
});
|
||||
|
||||
test('handles invalid timestamp format', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'my-stack',
|
||||
createdAt: 'invalid-date',
|
||||
updatedAt: 'not-a-date',
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.createdAt, isNull);
|
||||
expect(entity.updatedAt, isNull);
|
||||
});
|
||||
|
||||
test('parses valid ISO 8601 timestamps', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'my-stack',
|
||||
createdAt: '2024-06-15T14:30:00.000Z',
|
||||
updatedAt: '2024-06-16T09:15:30.500Z',
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.createdAt?.year, equals(2024));
|
||||
expect(entity.createdAt?.month, equals(6));
|
||||
expect(entity.createdAt?.day, equals(15));
|
||||
expect(entity.updatedAt?.hour, equals(9));
|
||||
expect(entity.updatedAt?.minute, equals(15));
|
||||
});
|
||||
});
|
||||
|
||||
group('_parseStackType', () {
|
||||
final testCases = {
|
||||
'compose': StackType.compose,
|
||||
'Compose': StackType.compose,
|
||||
'COMPOSE': StackType.compose,
|
||||
'swarm': StackType.swarm,
|
||||
'Swarm': StackType.swarm,
|
||||
'SWARM': StackType.swarm,
|
||||
'kubernetes': StackType.kubernetes,
|
||||
'Kubernetes': StackType.kubernetes,
|
||||
'KUBERNETES': StackType.kubernetes,
|
||||
'k8s': StackType.kubernetes,
|
||||
'K8S': StackType.kubernetes,
|
||||
'K8s': StackType.kubernetes,
|
||||
'unknown': StackType.compose, // Defaults to compose
|
||||
'': StackType.compose, // Empty defaults to compose
|
||||
'invalid': StackType.compose, // Invalid defaults to compose
|
||||
};
|
||||
|
||||
testCases.forEach((input, expected) {
|
||||
test('parses "$input" to ${expected.name}', () {
|
||||
final model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'test',
|
||||
typeString: input,
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.type, equals(expected));
|
||||
});
|
||||
});
|
||||
|
||||
test('handles null type', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'test',
|
||||
typeString: null,
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.type, equals(StackType.compose));
|
||||
});
|
||||
});
|
||||
|
||||
group('_parseStackStatus', () {
|
||||
final testCases = {
|
||||
'active': StackStatus.active,
|
||||
'Active': StackStatus.active,
|
||||
'ACTIVE': StackStatus.active,
|
||||
'running': StackStatus.active,
|
||||
'Running': StackStatus.active,
|
||||
'RUNNING': StackStatus.active,
|
||||
'inactive': StackStatus.inactive,
|
||||
'Inactive': StackStatus.inactive,
|
||||
'INACTIVE': StackStatus.inactive,
|
||||
'stopped': StackStatus.inactive,
|
||||
'Stopped': StackStatus.inactive,
|
||||
'STOPPED': StackStatus.inactive,
|
||||
'error': StackStatus.error,
|
||||
'Error': StackStatus.error,
|
||||
'ERROR': StackStatus.error,
|
||||
'unknown': StackStatus.unknown, // Defaults to unknown
|
||||
'': StackStatus.unknown, // Empty defaults to unknown
|
||||
'invalid': StackStatus.unknown, // Invalid defaults to unknown
|
||||
};
|
||||
|
||||
testCases.forEach((input, expected) {
|
||||
test('parses "$input" to ${expected.name}', () {
|
||||
final model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'test',
|
||||
statusString: input,
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.status, equals(expected));
|
||||
});
|
||||
});
|
||||
|
||||
test('handles null status', () {
|
||||
const model = StackModel(
|
||||
id: 'stack-123',
|
||||
name: 'test',
|
||||
statusString: null,
|
||||
);
|
||||
|
||||
final entity = model.toEntity();
|
||||
|
||||
expect(entity.status, equals(StackStatus.unknown));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('Stack entity', () {
|
||||
group('isHealthy', () {
|
||||
test('returns true when all containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'healthy-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 5,
|
||||
);
|
||||
|
||||
expect(stack.isHealthy, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when some containers are not running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'partial-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 3,
|
||||
);
|
||||
|
||||
expect(stack.isHealthy, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when no containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'stopped-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 0,
|
||||
);
|
||||
|
||||
expect(stack.isHealthy, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when containerCount is zero', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'empty-stack',
|
||||
containerCount: 0,
|
||||
runningCount: 0,
|
||||
);
|
||||
|
||||
expect(stack.isHealthy, isFalse);
|
||||
});
|
||||
});
|
||||
|
||||
group('hasRunningContainers', () {
|
||||
test('returns true when some containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'running-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 2,
|
||||
);
|
||||
|
||||
expect(stack.hasRunningContainers, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when no containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'stopped-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 0,
|
||||
);
|
||||
|
||||
expect(stack.hasRunningContainers, isFalse);
|
||||
});
|
||||
|
||||
test('returns true when all containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'healthy-stack',
|
||||
containerCount: 3,
|
||||
runningCount: 3,
|
||||
);
|
||||
|
||||
expect(stack.hasRunningContainers, isTrue);
|
||||
});
|
||||
});
|
||||
|
||||
group('isPartial', () {
|
||||
test('returns true when some but not all containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'partial-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 3,
|
||||
);
|
||||
|
||||
expect(stack.isPartial, isTrue);
|
||||
});
|
||||
|
||||
test('returns false when all containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'healthy-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 5,
|
||||
);
|
||||
|
||||
expect(stack.isPartial, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when no containers are running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'stopped-stack',
|
||||
containerCount: 5,
|
||||
runningCount: 0,
|
||||
);
|
||||
|
||||
expect(stack.isPartial, isFalse);
|
||||
});
|
||||
|
||||
test('returns false when containerCount is zero', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'empty-stack',
|
||||
containerCount: 0,
|
||||
runningCount: 0,
|
||||
);
|
||||
|
||||
expect(stack.isPartial, isFalse);
|
||||
});
|
||||
|
||||
test('returns true when only one of many containers is running', () {
|
||||
const stack = Stack(
|
||||
id: 'stack-1',
|
||||
name: 'degraded-stack',
|
||||
containerCount: 10,
|
||||
runningCount: 1,
|
||||
);
|
||||
|
||||
expect(stack.isPartial, isTrue);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user