feat: 接入起卦后端流程并完善积分扣减链路
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/apis/divination_api.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
|
||||
void main() {
|
||||
test('buildDivinationRunPayload contains required AG-UI fields', () {
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.auto,
|
||||
questionType: QuestionType.career,
|
||||
question: '什么时候找到工作',
|
||||
divinationTime: DateTime(2026, 4, 3, 18, 0, 2),
|
||||
coinBalance: 0,
|
||||
userId: 'u_test',
|
||||
);
|
||||
final payload = buildDivinationRunPayload(
|
||||
params: params,
|
||||
yaoStates: const <YaoType>[
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYang,
|
||||
YaoType.oldYang,
|
||||
YaoType.oldYin,
|
||||
YaoType.youngYang,
|
||||
],
|
||||
threadId: 'de44f2fb-de0a-46b9-bbf2-e99ee36f2a2d',
|
||||
runId: 'run_1775210431957',
|
||||
clientNow: DateTime(2026, 4, 3, 18, 0, 31, 958),
|
||||
);
|
||||
|
||||
expect(payload['state'], isA<Map<String, dynamic>>());
|
||||
expect(payload['tools'], isA<List<dynamic>>());
|
||||
expect(payload['context'], isA<List<dynamic>>());
|
||||
|
||||
final forwardedProps = payload['forwardedProps'] as Map<String, dynamic>;
|
||||
expect(forwardedProps['runtime_mode'], 'chat');
|
||||
final clientTime = forwardedProps['client_time'] as Map<String, dynamic>;
|
||||
expect((clientTime['client_now_iso'] as String).endsWith('Z'), isTrue);
|
||||
|
||||
final divinationPayload =
|
||||
forwardedProps['divinationPayload'] as Map<String, dynamic>;
|
||||
expect(
|
||||
(divinationPayload['divinationTimeIso'] as String).endsWith('Z'),
|
||||
isTrue,
|
||||
);
|
||||
expect((divinationPayload['yaoLines'] as List<dynamic>).length, 6);
|
||||
|
||||
final messages = payload['messages'] as List<dynamic>;
|
||||
expect(messages.length, 1);
|
||||
final userMessage = messages.first as Map<String, dynamic>;
|
||||
expect(userMessage['id'], isNotEmpty);
|
||||
expect(userMessage['role'], 'user');
|
||||
expect(userMessage['content'], '什么时候找到工作');
|
||||
});
|
||||
|
||||
test('buildDivinationRunPayload throws when yaoStates length is not 6', () {
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.auto,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试',
|
||||
divinationTime: DateTime(2026, 4, 3, 18, 0, 2),
|
||||
coinBalance: 0,
|
||||
userId: 'u_test',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => buildDivinationRunPayload(
|
||||
params: params,
|
||||
yaoStates: const <YaoType>[YaoType.youngYang],
|
||||
threadId: 'de44f2fb-de0a-46b9-bbf2-e99ee36f2a2d',
|
||||
runId: 'run_1775210431957',
|
||||
clientNow: DateTime(2026, 4, 3, 18, 0, 31, 958),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
});
|
||||
|
||||
test(
|
||||
'buildDivinationRunPayload throws when yaoStates contains undetermined',
|
||||
() {
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.auto,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试',
|
||||
divinationTime: DateTime(2026, 4, 3, 18, 0, 2),
|
||||
coinBalance: 0,
|
||||
userId: 'u_test',
|
||||
);
|
||||
|
||||
expect(
|
||||
() => buildDivinationRunPayload(
|
||||
params: params,
|
||||
yaoStates: const <YaoType>[
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYang,
|
||||
YaoType.oldYang,
|
||||
YaoType.oldYin,
|
||||
YaoType.undetermined,
|
||||
],
|
||||
threadId: 'de44f2fb-de0a-46b9-bbf2-e99ee36f2a2d',
|
||||
runId: 'run_1775210431957',
|
||||
clientNow: DateTime(2026, 4, 3, 18, 0, 31, 958),
|
||||
),
|
||||
throwsArgumentError,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_backend_models.dart';
|
||||
|
||||
void main() {
|
||||
test('YaoBackendLine accepts empty specialMark', () {
|
||||
final line = YaoBackendLine.fromJson(<String, dynamic>{
|
||||
'position': 1,
|
||||
'spiritName': '龙',
|
||||
'relationName': '父母',
|
||||
'tiganName': '卯',
|
||||
'elementName': '木',
|
||||
'isYang': true,
|
||||
'isChanging': false,
|
||||
'specialMark': '',
|
||||
});
|
||||
|
||||
expect(line.specialMark, '');
|
||||
});
|
||||
}
|
||||
@@ -2,8 +2,15 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
|
||||
void main() {
|
||||
test('mock data contains valid defaults', () {
|
||||
final params = DivinationMockData.initial();
|
||||
test('params contains valid fields', () {
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.manual,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试问题',
|
||||
divinationTime: DateTime(2026, 4, 3, 10, 30),
|
||||
coinBalance: 8,
|
||||
userId: 'u_test',
|
||||
);
|
||||
|
||||
expect(params.method, DivinationMethod.manual);
|
||||
expect(params.questionType, QuestionType.career);
|
||||
@@ -30,7 +37,14 @@ void main() {
|
||||
});
|
||||
|
||||
test('toBinary and toChangedBinary mappings are correct', () {
|
||||
final params = DivinationMockData.initial();
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.manual,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试问题',
|
||||
divinationTime: DateTime(2026, 4, 3, 10, 30),
|
||||
coinBalance: 8,
|
||||
userId: 'u_test',
|
||||
);
|
||||
final states = <YaoType>[
|
||||
YaoType.oldYin,
|
||||
YaoType.youngYang,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/services/divination_result_builder.dart';
|
||||
|
||||
void main() {
|
||||
final builder = DivinationResultBuilder();
|
||||
|
||||
test('build returns result with hexagram names and section text', () {
|
||||
final params = DivinationMockData.initial().copyWith(
|
||||
method: DivinationMethod.auto,
|
||||
question: '近期工作是否会有突破',
|
||||
questionType: QuestionType.career,
|
||||
);
|
||||
|
||||
final result = builder.build(
|
||||
params: params,
|
||||
yaoStates: const [
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYin,
|
||||
YaoType.oldYang,
|
||||
YaoType.youngYin,
|
||||
YaoType.oldYin,
|
||||
YaoType.youngYang,
|
||||
],
|
||||
);
|
||||
|
||||
expect(result.guaName, isNotEmpty);
|
||||
expect(result.targetGuaName, isNotEmpty);
|
||||
expect(result.binaryCode, hasLength(6));
|
||||
expect(result.changedBinaryCode, hasLength(6));
|
||||
expect(result.keywords, contains('签'));
|
||||
expect(result.conclusion, contains('这个卦象的结果为'));
|
||||
expect(result.yaoLines.length, 6);
|
||||
expect(result.targetYaoLines.length, 6);
|
||||
});
|
||||
}
|
||||
@@ -1,43 +1,180 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/app/app_theme.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/services/divination_result_builder.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_result.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/presentation/screens/divination_result_screen.dart';
|
||||
import 'package:meeyao_qianwen/l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('result screen shows key sections', (tester) async {
|
||||
final params = DivinationMockData.initial().copyWith(
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.auto,
|
||||
questionType: QuestionType.health,
|
||||
question: '近期状态是否平稳',
|
||||
divinationTime: DateTime(2026, 4, 3, 20, 30),
|
||||
coinBalance: 10,
|
||||
userId: 'u_test',
|
||||
);
|
||||
final data = DivinationResultBuilder().build(
|
||||
final data = DivinationResultData(
|
||||
params: params,
|
||||
yaoStates: const [
|
||||
YaoType.oldYin,
|
||||
YaoType.youngYang,
|
||||
YaoType.youngYin,
|
||||
YaoType.oldYang,
|
||||
YaoType.youngYang,
|
||||
YaoType.oldYin,
|
||||
binaryCode: '101001',
|
||||
changedBinaryCode: '100001',
|
||||
guaName: '山火贲',
|
||||
targetGuaName: '山雷颐',
|
||||
upperName: '艮',
|
||||
lowerName: '离',
|
||||
signType: '中上签',
|
||||
keywords: '稳中求进、审时度势、蓄势待发',
|
||||
conclusion: '1. 方向可行\n2. 节奏宜稳',
|
||||
analysis: '当前阶段需先稳住节奏,再做关键推进。',
|
||||
suggestion: '1. 控节奏\n2. 重复盘',
|
||||
ganzhi: GanzhiData(
|
||||
yearGanZhi: '丙午',
|
||||
monthGanZhi: '辛卯',
|
||||
dayGanZhi: '丁未',
|
||||
timeGanZhi: '庚戌',
|
||||
yearKongWang: '子丑',
|
||||
monthKongWang: '戌亥',
|
||||
dayKongWang: '寅卯',
|
||||
timeKongWang: '寅卯',
|
||||
yueJian: '卯木',
|
||||
riChen: '未土',
|
||||
yuePo: '酉金',
|
||||
riChong: '丑土',
|
||||
),
|
||||
wuXingStatus: {'木': '旺', '火': '相', '土': '休', '金': '囚', '水': '死'},
|
||||
yaoLines: [
|
||||
YaoLineData(
|
||||
index: 0,
|
||||
spirit: '龙',
|
||||
relation: '父母',
|
||||
branch: '卯',
|
||||
element: '木',
|
||||
type: YaoType.oldYin,
|
||||
mark: '世',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 1,
|
||||
spirit: '雀',
|
||||
relation: '兄弟',
|
||||
branch: '丑',
|
||||
element: '土',
|
||||
type: YaoType.youngYang,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 2,
|
||||
spirit: '勾',
|
||||
relation: '妻财',
|
||||
branch: '亥',
|
||||
element: '水',
|
||||
type: YaoType.youngYin,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 3,
|
||||
spirit: '蛇',
|
||||
relation: '妻财',
|
||||
branch: '酉',
|
||||
element: '金',
|
||||
type: YaoType.oldYang,
|
||||
mark: '应',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 4,
|
||||
spirit: '虎',
|
||||
relation: '子孙',
|
||||
branch: '未',
|
||||
element: '土',
|
||||
type: YaoType.youngYang,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 5,
|
||||
spirit: '玄',
|
||||
relation: '兄弟',
|
||||
branch: '巳',
|
||||
element: '火',
|
||||
type: YaoType.oldYin,
|
||||
mark: '',
|
||||
),
|
||||
],
|
||||
targetYaoLines: [
|
||||
YaoLineData(
|
||||
index: 0,
|
||||
spirit: '龙',
|
||||
relation: '父母',
|
||||
branch: '子',
|
||||
element: '水',
|
||||
type: YaoType.youngYang,
|
||||
mark: '世',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 1,
|
||||
spirit: '雀',
|
||||
relation: '兄弟',
|
||||
branch: '丑',
|
||||
element: '土',
|
||||
type: YaoType.youngYang,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 2,
|
||||
spirit: '勾',
|
||||
relation: '妻财',
|
||||
branch: '亥',
|
||||
element: '水',
|
||||
type: YaoType.youngYin,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 3,
|
||||
spirit: '蛇',
|
||||
relation: '妻财',
|
||||
branch: '申',
|
||||
element: '金',
|
||||
type: YaoType.youngYin,
|
||||
mark: '应',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 4,
|
||||
spirit: '虎',
|
||||
relation: '子孙',
|
||||
branch: '未',
|
||||
element: '土',
|
||||
type: YaoType.youngYang,
|
||||
mark: '',
|
||||
),
|
||||
YaoLineData(
|
||||
index: 5,
|
||||
spirit: '玄',
|
||||
relation: '兄弟',
|
||||
branch: '午',
|
||||
element: '火',
|
||||
type: YaoType.youngYang,
|
||||
mark: '',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: DivinationResultScreen(data: data),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.text('天机推演中'), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 450));
|
||||
expect(find.text('正在解卦'), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 850));
|
||||
expect(find.text('解卦完成\n点击查看'), findsOneWidget);
|
||||
await tester.pump(const Duration(milliseconds: 1000));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('解卦结果'), findsOneWidget);
|
||||
expect(find.text('AI解卦'), findsOneWidget);
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/app/app_theme.dart';
|
||||
import 'package:meeyao_qianwen/core/auth/session_store.dart';
|
||||
import 'package:meeyao_qianwen/data/network/api_client.dart';
|
||||
import 'package:meeyao_qianwen/data/storage/local_kv_store.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/apis/divination_api.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/services/divination_run_service.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/presentation/screens/auto_divination_screen.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/presentation/screens/divination_screen.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/presentation/screens/manual_divination_screen.dart';
|
||||
import 'package:meeyao_qianwen/l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
final runService = DivinationRunService(
|
||||
api: DivinationApi(apiClient: ApiClient(baseUrl: 'http://localhost:5775')),
|
||||
);
|
||||
final sessionStore = SessionStore(LocalKvStore());
|
||||
|
||||
testWidgets('divination screen navigates to auto screen', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(theme: AppTheme.light(), home: const DivinationScreen()),
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: DivinationScreen(
|
||||
sessionStore: sessionStore,
|
||||
userId: 'user_test',
|
||||
runServiceOverride: runService,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('自动起卦'));
|
||||
@@ -25,15 +52,27 @@ void main() {
|
||||
testWidgets('auto screen keeps resolve button disabled initially', (
|
||||
tester,
|
||||
) async {
|
||||
final params = DivinationMockData.initial().copyWith(
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.auto,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试问题',
|
||||
divinationTime: DateTime(2026, 4, 3, 20, 30),
|
||||
coinBalance: 9,
|
||||
userId: 'user_test',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: AutoDivinationScreen(params: params),
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: AutoDivinationScreen(params: params, runService: runService),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -49,7 +88,22 @@ void main() {
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(theme: AppTheme.light(), home: const DivinationScreen()),
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: DivinationScreen(
|
||||
sessionStore: sessionStore,
|
||||
userId: 'user_test',
|
||||
runServiceOverride: runService,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.enterText(find.byType(TextField), '近期感情是否稳定');
|
||||
|
||||
@@ -1,20 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meeyao_qianwen/app/app_theme.dart';
|
||||
import 'package:meeyao_qianwen/data/network/api_client.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/apis/divination_api.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/models/divination_params.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/data/services/divination_run_service.dart';
|
||||
import 'package:meeyao_qianwen/features/divination/presentation/screens/manual_divination_screen.dart';
|
||||
import 'package:meeyao_qianwen/l10n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('manual screen shows yao legend', (tester) async {
|
||||
final params = DivinationMockData.initial().copyWith(
|
||||
final params = DivinationParams(
|
||||
method: DivinationMethod.manual,
|
||||
questionType: QuestionType.career,
|
||||
question: '测试问题',
|
||||
divinationTime: DateTime(2026, 4, 3, 20, 30),
|
||||
coinBalance: 9,
|
||||
userId: 'user_test',
|
||||
);
|
||||
final runService = DivinationRunService(
|
||||
api: DivinationApi(
|
||||
apiClient: ApiClient(baseUrl: 'http://localhost:5775'),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: ManualDivinationScreen(params: params),
|
||||
localizationsDelegates: const [
|
||||
AppLocalizations.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: ManualDivinationScreen(params: params, runService: runService),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user