feat: 添加自动化任务(automation_jobs)功能模块
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:social_app/features/settings/data/models/automation_job_model.dart';
|
||||
|
||||
void main() {
|
||||
group('MessageContextConfigModel', () {
|
||||
test('fromJson parses all fields correctly', () {
|
||||
final json = {
|
||||
'source': 'messages',
|
||||
'window_mode': 'week',
|
||||
'window_count': 5,
|
||||
};
|
||||
|
||||
final model = MessageContextConfigModel.fromJson(json);
|
||||
|
||||
expect(model.source, 'messages');
|
||||
expect(model.windowMode, 'week');
|
||||
expect(model.windowCount, 5);
|
||||
});
|
||||
|
||||
test('fromJson uses defaults for missing fields', () {
|
||||
final model = MessageContextConfigModel.fromJson(null);
|
||||
|
||||
expect(model.source, 'latest_chat');
|
||||
expect(model.windowMode, 'day');
|
||||
expect(model.windowCount, 2);
|
||||
});
|
||||
|
||||
test('toJson serializes correctly', () {
|
||||
final model = MessageContextConfigModel(
|
||||
source: 'messages',
|
||||
windowMode: 'week',
|
||||
windowCount: 5,
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
expect(json['source'], 'messages');
|
||||
expect(json['window_mode'], 'week');
|
||||
expect(json['window_count'], 5);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobConfigModel', () {
|
||||
test('fromJson parses all fields correctly', () {
|
||||
final json = {
|
||||
'input_template': 'Hello {{name}}',
|
||||
'enabled_tools': ['tool1', 'tool2'],
|
||||
'context': {
|
||||
'source': 'messages',
|
||||
'window_mode': 'week',
|
||||
'window_count': 5,
|
||||
},
|
||||
};
|
||||
|
||||
final model = AutomationJobConfigModel.fromJson(json);
|
||||
|
||||
expect(model.inputTemplate, 'Hello {{name}}');
|
||||
expect(model.enabledTools, ['tool1', 'tool2']);
|
||||
expect(model.context.source, 'messages');
|
||||
expect(model.context.windowMode, 'week');
|
||||
expect(model.context.windowCount, 5);
|
||||
});
|
||||
|
||||
test('fromJson uses defaults for null input', () {
|
||||
final model = AutomationJobConfigModel.fromJson(null);
|
||||
|
||||
expect(model.inputTemplate, '');
|
||||
expect(model.enabledTools, []);
|
||||
expect(model.context.source, 'latest_chat');
|
||||
expect(model.context.windowMode, 'day');
|
||||
expect(model.context.windowCount, 2);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobModel', () {
|
||||
test('fromJson parses all fields correctly', () {
|
||||
final json = {
|
||||
'id': 'job-123',
|
||||
'owner_id': 'user-456',
|
||||
'bootstrap_key': 'key-789',
|
||||
'title': 'Daily Report',
|
||||
'schedule_type': 'DAILY',
|
||||
'run_at': '09:00:00',
|
||||
'timezone': 'America/New_York',
|
||||
'status': 'ACTIVE',
|
||||
'is_system': false,
|
||||
'config': {
|
||||
'input_template': 'Hello',
|
||||
'enabled_tools': ['tool1'],
|
||||
'context': {
|
||||
'source': 'latest_chat',
|
||||
'window_mode': 'day',
|
||||
'window_count': 2,
|
||||
},
|
||||
},
|
||||
'next_run_at': '2024-01-15T09:00:00Z',
|
||||
'last_run_at': '2024-01-14T09:00:00Z',
|
||||
'created_at': '2024-01-01T00:00:00Z',
|
||||
'updated_at': '2024-01-14T12:00:00Z',
|
||||
};
|
||||
|
||||
final model = AutomationJobModel.fromJson(json);
|
||||
|
||||
expect(model.id, 'job-123');
|
||||
expect(model.ownerId, 'user-456');
|
||||
expect(model.bootstrapKey, 'key-789');
|
||||
expect(model.title, 'Daily Report');
|
||||
expect(model.scheduleType, 'DAILY');
|
||||
expect(model.runAt, '09:00:00');
|
||||
expect(model.timezone, 'America/New_York');
|
||||
expect(model.status, 'ACTIVE');
|
||||
expect(model.isSystem, false);
|
||||
expect(model.config.inputTemplate, 'Hello');
|
||||
expect(model.config.enabledTools, ['tool1']);
|
||||
expect(model.config.context.windowCount, 2);
|
||||
expect(model.nextRunAt, DateTime.parse('2024-01-15T09:00:00Z'));
|
||||
expect(model.lastRunAt, DateTime.parse('2024-01-14T09:00:00Z'));
|
||||
expect(model.createdAt, DateTime.parse('2024-01-01T00:00:00Z'));
|
||||
expect(model.updatedAt, DateTime.parse('2024-01-14T12:00:00Z'));
|
||||
});
|
||||
|
||||
test('fromJson throws for missing required date fields', () {
|
||||
final json = <String, dynamic>{
|
||||
'id': 'job-123',
|
||||
'owner_id': 'user-456',
|
||||
'title': 'Test',
|
||||
'schedule_type': 'DAILY',
|
||||
'run_at': '09:00:00',
|
||||
'timezone': 'UTC',
|
||||
'status': 'ACTIVE',
|
||||
'is_system': false,
|
||||
'config': null,
|
||||
};
|
||||
|
||||
expect(
|
||||
() => AutomationJobModel.fromJson(json),
|
||||
throwsA(isA<FormatException>()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobConfigPatchModel', () {
|
||||
test('toJson only includes non-null fields', () {
|
||||
final model = AutomationJobConfigPatchModel(
|
||||
inputTemplate: 'Updated template',
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
expect(json.containsKey('input_template'), true);
|
||||
expect(json.containsKey('enabled_tools'), false);
|
||||
expect(json.containsKey('context'), false);
|
||||
expect(json['input_template'], 'Updated template');
|
||||
});
|
||||
|
||||
test('toJson includes all fields when set', () {
|
||||
final model = AutomationJobConfigPatchModel(
|
||||
inputTemplate: 'Template',
|
||||
enabledTools: ['tool1', 'tool2'],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'messages',
|
||||
windowMode: 'week',
|
||||
windowCount: 3,
|
||||
),
|
||||
);
|
||||
|
||||
final json = model.toJson();
|
||||
|
||||
expect(json['input_template'], 'Template');
|
||||
expect(json['enabled_tools'], ['tool1', 'tool2']);
|
||||
expect(json['context'], {
|
||||
'source': 'messages',
|
||||
'window_mode': 'week',
|
||||
'window_count': 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobUpdateRequest', () {
|
||||
test('toJson only includes non-null fields', () {
|
||||
final request = AutomationJobUpdateRequest(
|
||||
title: 'Updated Title',
|
||||
status: 'INACTIVE',
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
|
||||
expect(json.containsKey('title'), true);
|
||||
expect(json.containsKey('status'), true);
|
||||
expect(json.containsKey('schedule_type'), false);
|
||||
expect(json.containsKey('run_at'), false);
|
||||
expect(json['title'], 'Updated Title');
|
||||
expect(json['status'], 'INACTIVE');
|
||||
});
|
||||
|
||||
test('toJson includes patch config with only non-null fields', () {
|
||||
final request = AutomationJobUpdateRequest(
|
||||
config: AutomationJobConfigPatchModel(inputTemplate: 'New template'),
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
|
||||
expect(json.containsKey('config'), true);
|
||||
final configJson = json['config'] as Map<String, dynamic>;
|
||||
expect(configJson.containsKey('input_template'), true);
|
||||
expect(configJson.containsKey('enabled_tools'), false);
|
||||
expect(configJson.containsKey('context'), false);
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobCreateRequest', () {
|
||||
test('toJson serializes correctly', () {
|
||||
final request = AutomationJobCreateRequest(
|
||||
title: 'New Job',
|
||||
scheduleType: 'DAILY',
|
||||
runAt: '10:00:00',
|
||||
timezone: 'UTC',
|
||||
status: 'ACTIVE',
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: 'Hello',
|
||||
enabledTools: ['tool1'],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final json = request.toJson();
|
||||
|
||||
expect(json['title'], 'New Job');
|
||||
expect(json['schedule_type'], 'DAILY');
|
||||
expect(json['run_at'], '10:00:00');
|
||||
expect(json['timezone'], 'UTC');
|
||||
expect(json['status'], 'ACTIVE');
|
||||
expect(json['config'], {
|
||||
'input_template': 'Hello',
|
||||
'enabled_tools': ['tool1'],
|
||||
'context': {
|
||||
'source': 'latest_chat',
|
||||
'window_mode': 'day',
|
||||
'window_count': 2,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('AutomationJobListResponse', () {
|
||||
test('fromJson parses items correctly', () {
|
||||
final json = {
|
||||
'items': [
|
||||
{
|
||||
'id': 'job-1',
|
||||
'owner_id': 'user-1',
|
||||
'title': 'Job 1',
|
||||
'schedule_type': 'DAILY',
|
||||
'run_at': '09:00:00',
|
||||
'timezone': 'UTC',
|
||||
'status': 'ACTIVE',
|
||||
'is_system': false,
|
||||
'config': null,
|
||||
'next_run_at': '2024-01-15T09:00:00Z',
|
||||
'created_at': '2024-01-01T00:00:00Z',
|
||||
'updated_at': '2024-01-14T12:00:00Z',
|
||||
},
|
||||
{
|
||||
'id': 'job-2',
|
||||
'owner_id': 'user-1',
|
||||
'title': 'Job 2',
|
||||
'schedule_type': 'HOURLY',
|
||||
'run_at': '00:00:00',
|
||||
'timezone': 'UTC',
|
||||
'status': 'INACTIVE',
|
||||
'is_system': false,
|
||||
'config': null,
|
||||
'next_run_at': '2024-01-15T10:00:00Z',
|
||||
'created_at': '2024-01-02T00:00:00Z',
|
||||
'updated_at': '2024-01-14T12:00:00Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
final response = AutomationJobListResponse.fromJson(json);
|
||||
|
||||
expect(response.items.length, 2);
|
||||
expect(response.items[0].id, 'job-1');
|
||||
expect(response.items[1].id, 'job-2');
|
||||
});
|
||||
|
||||
test('fromJson returns empty list for null items', () {
|
||||
final response = AutomationJobListResponse.fromJson(null);
|
||||
|
||||
expect(response.items, isEmpty);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:social_app/features/settings/data/models/automation_job_model.dart';
|
||||
import 'package:social_app/features/settings/data/services/automation_jobs_api.dart';
|
||||
import 'package:social_app/features/settings/presentation/cubits/automation_jobs_cubit.dart';
|
||||
|
||||
class MockAutomationJobsApi extends Mock implements AutomationJobsApi {}
|
||||
|
||||
class FakeAutomationJobUpdateRequest extends Fake
|
||||
implements AutomationJobUpdateRequest {}
|
||||
|
||||
void main() {
|
||||
late AutomationJobsCubit cubit;
|
||||
late MockAutomationJobsApi mockApi;
|
||||
|
||||
final testJob = AutomationJobModel(
|
||||
id: '1',
|
||||
ownerId: 'owner1',
|
||||
title: 'Test Job',
|
||||
scheduleType: 'DAILY',
|
||||
runAt: '08:00:00',
|
||||
timezone: 'UTC',
|
||||
status: 'ACTIVE',
|
||||
isSystem: false,
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: '',
|
||||
enabledTools: const [],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
),
|
||||
nextRunAt: DateTime(2024, 1, 1),
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(FakeAutomationJobUpdateRequest());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockApi = MockAutomationJobsApi();
|
||||
cubit = AutomationJobsCubit(mockApi);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
cubit.close();
|
||||
});
|
||||
|
||||
group('AutomationJobsCubit', () {
|
||||
test('initial state is correct', () {
|
||||
expect(cubit.state.jobs, isEmpty);
|
||||
expect(cubit.state.isLoading, isFalse);
|
||||
expect(cubit.state.error, isNull);
|
||||
});
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'loadJobs success emits loading then jobs',
|
||||
build: () {
|
||||
when(() => mockApi.list()).thenAnswer((_) async => [testJob]);
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.loadJobs(),
|
||||
expect: () => [
|
||||
isA<AutomationJobsState>().having(
|
||||
(s) => s.isLoading,
|
||||
'isLoading',
|
||||
true,
|
||||
),
|
||||
isA<AutomationJobsState>()
|
||||
.having((s) => s.isLoading, 'isLoading', false)
|
||||
.having((s) => s.jobs, 'jobs', [testJob]),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'loadJobs failure emits loading then error',
|
||||
build: () {
|
||||
when(() => mockApi.list()).thenThrow(Exception('Network error'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.loadJobs(),
|
||||
expect: () => [
|
||||
isA<AutomationJobsState>().having(
|
||||
(s) => s.isLoading,
|
||||
'isLoading',
|
||||
true,
|
||||
),
|
||||
isA<AutomationJobsState>()
|
||||
.having((s) => s.isLoading, 'isLoading', false)
|
||||
.having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'deleteJob success calls loadJobs to refresh',
|
||||
build: () {
|
||||
when(() => mockApi.delete(any())).thenAnswer((_) async {});
|
||||
when(() => mockApi.list()).thenAnswer((_) async => []);
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.deleteJob('1'),
|
||||
verify: (_) {
|
||||
verify(() => mockApi.delete('1')).called(1);
|
||||
verify(() => mockApi.list()).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'deleteJob failure emits error without refreshing',
|
||||
build: () {
|
||||
when(() => mockApi.delete(any())).thenThrow(Exception('Delete failed'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.deleteJob('1'),
|
||||
expect: () => [
|
||||
isA<AutomationJobsState>().having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(() => mockApi.delete('1')).called(1);
|
||||
verifyNever(() => mockApi.list());
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'updateJobStatus success replaces target job',
|
||||
build: () {
|
||||
when(
|
||||
() => mockApi.update(any(), any()),
|
||||
).thenAnswer((_) async => testJob.copyWith(status: 'disabled'));
|
||||
return cubit;
|
||||
},
|
||||
seed: () => AutomationJobsState(jobs: [testJob]),
|
||||
act: (c) => c.updateJobStatus(id: '1', enabled: false),
|
||||
expect: () => [
|
||||
isA<AutomationJobsState>().having(
|
||||
(s) => s.jobs.first.status,
|
||||
'updated status',
|
||||
'disabled',
|
||||
),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(() => mockApi.update('1', any())).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<AutomationJobsCubit, AutomationJobsState>(
|
||||
'updateJobStatus failure emits error',
|
||||
build: () {
|
||||
when(
|
||||
() => mockApi.update(any(), any()),
|
||||
).thenThrow(Exception('Update failed'));
|
||||
return cubit;
|
||||
},
|
||||
seed: () => AutomationJobsState(jobs: [testJob]),
|
||||
act: (c) => c.updateJobStatus(id: '1', enabled: false),
|
||||
expect: () => [
|
||||
isA<AutomationJobsState>().having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'package:bloc_test/bloc_test.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:social_app/features/settings/data/models/automation_job_model.dart';
|
||||
import 'package:social_app/features/settings/data/services/automation_jobs_api.dart';
|
||||
import 'package:social_app/features/settings/presentation/cubits/job_detail_cubit.dart';
|
||||
|
||||
class MockAutomationJobsApi extends Mock implements AutomationJobsApi {}
|
||||
|
||||
class FakeAutomationJobUpdateRequest extends Fake
|
||||
implements AutomationJobUpdateRequest {}
|
||||
|
||||
class FakeAutomationJobCreateRequest extends Fake
|
||||
implements AutomationJobCreateRequest {}
|
||||
|
||||
void main() {
|
||||
late JobDetailCubit cubit;
|
||||
late MockAutomationJobsApi mockApi;
|
||||
|
||||
final testJob = AutomationJobModel(
|
||||
id: '1',
|
||||
ownerId: 'owner1',
|
||||
title: 'Test Job',
|
||||
scheduleType: 'DAILY',
|
||||
runAt: '08:00:00',
|
||||
timezone: 'UTC',
|
||||
status: 'ACTIVE',
|
||||
isSystem: false,
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: '',
|
||||
enabledTools: const [],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
),
|
||||
nextRunAt: DateTime(2024, 1, 1),
|
||||
createdAt: DateTime(2024, 1, 1),
|
||||
updatedAt: DateTime(2024, 1, 1),
|
||||
);
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(FakeAutomationJobUpdateRequest());
|
||||
registerFallbackValue(FakeAutomationJobCreateRequest());
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
mockApi = MockAutomationJobsApi();
|
||||
cubit = JobDetailCubit(mockApi);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
cubit.close();
|
||||
});
|
||||
|
||||
group('JobDetailCubit', () {
|
||||
test('initial state is correct', () {
|
||||
expect(cubit.state.job, isNull);
|
||||
expect(cubit.state.isLoading, isFalse);
|
||||
expect(cubit.state.isSaving, isFalse);
|
||||
expect(cubit.state.error, isNull);
|
||||
});
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'loadJob success emits loading then job',
|
||||
build: () {
|
||||
when(() => mockApi.get(any())).thenAnswer((_) async => testJob);
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.loadJob('1'),
|
||||
expect: () => [
|
||||
isA<JobDetailState>().having((s) => s.isLoading, 'isLoading', true),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isLoading, 'isLoading', false)
|
||||
.having((s) => s.job, 'job', testJob),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'loadJob failure emits loading then error',
|
||||
build: () {
|
||||
when(() => mockApi.get(any())).thenThrow(Exception('Network error'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.loadJob('1'),
|
||||
expect: () => [
|
||||
isA<JobDetailState>().having((s) => s.isLoading, 'isLoading', true),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isLoading, 'isLoading', false)
|
||||
.having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'updateJob success emits saving then job with saving false',
|
||||
build: () {
|
||||
when(
|
||||
() => mockApi.update(any(), any()),
|
||||
).thenAnswer((_) async => testJob);
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.updateJob('1', AutomationJobUpdateRequest()),
|
||||
expect: () => [
|
||||
isA<JobDetailState>().having((s) => s.isSaving, 'isSaving', true),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', false)
|
||||
.having((s) => s.job, 'job', testJob),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'updateJob failure emits saving then error',
|
||||
build: () {
|
||||
when(
|
||||
() => mockApi.update(any(), any()),
|
||||
).thenThrow(Exception('Update failed'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.updateJob('1', AutomationJobUpdateRequest()),
|
||||
expect: () => [
|
||||
isA<JobDetailState>().having((s) => s.isSaving, 'isSaving', true),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', false)
|
||||
.having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'deleteJob success emits saving then saving false',
|
||||
build: () {
|
||||
when(() => mockApi.delete(any())).thenAnswer((_) async {});
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.deleteJob('1'),
|
||||
expect: () => [
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', true)
|
||||
.having((s) => s.error, 'error', isNull),
|
||||
isA<JobDetailState>().having((s) => s.isSaving, 'isSaving', false),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(() => mockApi.delete('1')).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'deleteJob failure emits saving then error',
|
||||
build: () {
|
||||
when(() => mockApi.delete(any())).thenThrow(Exception('Delete failed'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.deleteJob('1'),
|
||||
expect: () => [
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', true)
|
||||
.having((s) => s.error, 'error', isNull),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', false)
|
||||
.having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(() => mockApi.delete('1')).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'createJob success emits saving then created job',
|
||||
build: () {
|
||||
when(() => mockApi.create(any())).thenAnswer((_) async => testJob);
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.createJob(
|
||||
AutomationJobCreateRequest(
|
||||
title: 'New Job',
|
||||
scheduleType: 'daily',
|
||||
runAt: '08:00:00',
|
||||
timezone: 'Asia/Shanghai',
|
||||
status: 'active',
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: 'hello',
|
||||
enabledTools: const ['memory.write'],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expect: () => [
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', true)
|
||||
.having((s) => s.error, 'error', isNull),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', false)
|
||||
.having((s) => s.job, 'job', testJob),
|
||||
],
|
||||
verify: (_) {
|
||||
verify(() => mockApi.create(any())).called(1);
|
||||
},
|
||||
);
|
||||
|
||||
blocTest<JobDetailCubit, JobDetailState>(
|
||||
'createJob failure emits saving then error',
|
||||
build: () {
|
||||
when(() => mockApi.create(any())).thenThrow(Exception('Create failed'));
|
||||
return cubit;
|
||||
},
|
||||
act: (c) => c.createJob(
|
||||
AutomationJobCreateRequest(
|
||||
title: 'New Job',
|
||||
scheduleType: 'daily',
|
||||
runAt: '08:00:00',
|
||||
timezone: 'Asia/Shanghai',
|
||||
status: 'active',
|
||||
config: AutomationJobConfigModel(
|
||||
inputTemplate: 'hello',
|
||||
enabledTools: const ['memory.write'],
|
||||
context: MessageContextConfigModel(
|
||||
source: 'latest_chat',
|
||||
windowMode: 'day',
|
||||
windowCount: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
expect: () => [
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', true)
|
||||
.having((s) => s.error, 'error', isNull),
|
||||
isA<JobDetailState>()
|
||||
.having((s) => s.isSaving, 'isSaving', false)
|
||||
.having((s) => s.error, 'error', isNotNull),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user