From 5d6a9617aaefbbaeedbd01ce82b447cb5819084b Mon Sep 17 00:00:00 2001 From: toki Date: Sat, 30 May 2026 16:18:29 +0900 Subject: [PATCH] refactor: update socket client and clean up backtest analysis files --- .../CODE_REVIEW-cloud-G06.md | 133 ---------- .../PLAN-cloud-G06.md | 229 ------------------ .../socket/alt_socket_client.dart | 42 ++++ .../socket/alt_socket_client_test.dart | 178 ++++++++++++++ 4 files changed, 220 insertions(+), 362 deletions(-) delete mode 100644 agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/CODE_REVIEW-cloud-G06.md delete mode 100644 agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/PLAN-cloud-G06.md diff --git a/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/CODE_REVIEW-cloud-G06.md b/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/CODE_REVIEW-cloud-G06.md deleted file mode 100644 index 1beafda..0000000 --- a/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/CODE_REVIEW-cloud-G06.md +++ /dev/null @@ -1,133 +0,0 @@ - - -# Code Review Reference - BAS-BOUNDARY - -> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** -> The task is NOT complete until every implementation-owned section below is filled in. -> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. -> Fill implementation-owned sections, then stop with active files in place and report ready for review. -> If implementation is blocked by a user-only decision, external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. -> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. -> Follow the ownership table at the bottom of this file for which sections you own. - -## 개요 - -date=2026-05-30 -task=m-backtest-analysis-surface/03+01,02_api_client_boundary, plan=0, tag=BAS-BOUNDARY - -## 이 파일을 읽는 리뷰 에이전트에게 - -> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. - -각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. - -1. 판정을 append한다. -2. active plan/review files를 `.log`로 아카이브한다. -3. PASS이면 `complete.log` 작성 후 `agent-task/archive/YYYY/MM/m-backtest-analysis-surface/03+01,02_api_client_boundary/`로 이동한다. -4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다. - ---- - -## 구현 항목별 완료 여부 - -| 항목 | 완료 여부 | -|------|---------| -| [BAS-BOUNDARY-1] Align API parser boundary | [ ] | -| [BAS-BOUNDARY-2] Add typed client request helpers | [ ] | -| [BAS-BOUNDARY-3] Test backtest socket helpers | [ ] | -| [BAS-BOUNDARY-4] Boundary verification | [ ] | - -## 구현 체크리스트 - -- [ ] [BAS-BOUNDARY-1] API parser map and tests include all analysis request/response messages if not already done by contracts task. -- [ ] [BAS-BOUNDARY-2] `AltSocketClient` exposes typed backtest list/detail/result/compare request helpers. -- [ ] [BAS-BOUNDARY-3] Flutter socket client tests verify request type names, payloads, and response parsing. 검증: worker/API/client 경계가 backtest result를 일관되게 다룬다. -- [ ] [BAS-BOUNDARY-4] `go test ./services/api/...`, `cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart`, `bin/test`, `bin/lint`를 실행한다. 검증: `bin/test`와 `bin/lint`가 통과한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -## 코드리뷰 전용 체크리스트 - -> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. -> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. - -- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. -- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. -- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다. -- [ ] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다. -- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. -- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. -- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. -- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. -- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. -- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. -- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. - -## 계획 대비 변경 사항 - -_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ - -## 주요 설계 결정 - -_구현 에이전트가 주요 설계 결정 사항을 기록한다._ - -## 사용자 리뷰 요청 - -_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._ - -- 상태: 없음 -- 사유 유형: 없음 -- 결정 필요: 없음 -- 차단 근거: 없음 -- 실행한 검증/명령: 없음 -- 재개 조건: 없음 - -## 리뷰어를 위한 체크포인트 - -- Client helper request/response generic types match generated protobuf messages. -- Fake websocket tests assert `PacketBase.typeName`, request payload, and parsed response. -- API parser map remains aligned with client parser map for analysis messages. -- No worker internals are imported into API or client. - -## 검증 결과 - -_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ - -필수 규칙: -- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. -- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. -- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. -- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. - -### BAS-BOUNDARY-1 중간 검증 -```text -$ go test ./services/api/... -(output) -``` - -### BAS-BOUNDARY-2 중간 검증 -```text -$ cd apps/client && flutter analyze --no-fatal-infos -(output) -``` - -### BAS-BOUNDARY-3 중간 검증 -```text -$ cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart -(output) -``` - -### 최종 검증 -```text -$ go test ./services/api/... -$ cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart -$ bin/test -$ bin/lint -$ git diff --check -(output) -``` - ---- - -> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** -> If anything is blank, go back and fill it in before saving this file. -> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/PLAN-cloud-G06.md b/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/PLAN-cloud-G06.md deleted file mode 100644 index 4cf6ea6..0000000 --- a/agent-task/m-backtest-analysis-surface/03+01,02_api_client_boundary/PLAN-cloud-G06.md +++ /dev/null @@ -1,229 +0,0 @@ - - -# Plan - BAS-BOUNDARY - -## 이 파일을 읽는 구현 에이전트에게 - -`CODE_REVIEW-cloud-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 사용자 결정, 외부 환경 준비, 범위 충돌이 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 남기고 멈춘다. archive, `complete.log`, 최종 판정은 code-review 전용이다. - -## 배경 - -Contract와 worker storage가 준비되더라도 operator client가 typed backtest query 요청을 보낼 수 없으면 analysis surface가 실제 사용 흐름으로 연결되지 않는다. 이 계획은 API parser boundary와 Flutter socket client request helpers를 좁게 연결해 run list/detail/compare 표면의 첫 호출 경계를 만든다. - -## 사용자 리뷰 요청 흐름 - -구현 중 차단 조건은 active `CODE_REVIEW-cloud-G06.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 이를 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. - -## 분석 결과 - -### 읽은 파일 - -- `agent-roadmap/current.md` -- `agent-roadmap/phase/backtest-loop/milestones/backtest-analysis-surface.md` -- `services/api/internal/contracts/parser_map.go` -- `services/api/internal/contracts/parser_map_test.go` -- `apps/client/lib/src/contracts/alt_contracts.dart` -- `apps/client/test/contracts/alt_contracts_test.dart` -- `apps/client/lib/src/integrations/socket/alt_socket_client.dart` -- `apps/client/test/integrations/socket/alt_socket_client_test.dart` -- `packages/contracts/proto/alt/v1/backtest.proto` -- `bin/test` -- `bin/lint` -- `services/api/go.mod` -- `apps/client/pubspec.yaml` - -### 테스트 커버리지 공백 - -- `AltSocketClient` currently tests only `hello()` request/response at `apps/client/test/integrations/socket/alt_socket_client_test.dart:113`. -- No client test sends backtest run list/detail/result/compare requests through proto-socket. -- API parser tests cover current message types only and must remain aligned with generated messages from `01_contract_surface`. - -### 심볼 참조 - -- Renamed/removed symbols: none planned. -- New client methods will call existing inherited `sendRequest` in `AltSocketClient`, currently used by `hello()` at `apps/client/lib/src/integrations/socket/alt_socket_client.dart:32`. - -### 분할 판단 - -- Split policy evaluated before writing plan files. -- This task depends on `01_contract_surface` and `02+01_worker_analysis_store`. -- It is separate because API/client boundary tests can be reviewed independently after schema and storage behavior exist. -- It does not create full Flutter operator UI; that belongs to later `Flutter Operator Console`. - -### 범위 결정 근거 - -- Include API parser alignment if new messages were not already covered by `01_contract_surface`, typed client request helpers, and socket client tests. -- Exclude server-side business handlers because current API service only owns parser/socket boundary; worker owns execution and storage. -- Exclude dashboard UI and navigation. -- Exclude migrations/storage behavior already covered by `02+01_worker_analysis_store`. - -### 빌드 등급 - -- build lane: `cloud-G06`; review lane: `cloud-G06`. -- Rationale: cross-language boundary helpers and tests, but no storage or schema design should remain by this stage. - -## 구현 체크리스트 - -- [ ] [BAS-BOUNDARY-1] API parser map and tests include all analysis request/response messages if not already done by contracts task. -- [ ] [BAS-BOUNDARY-2] `AltSocketClient` exposes typed backtest list/detail/result/compare request helpers. -- [ ] [BAS-BOUNDARY-3] Flutter socket client tests verify request type names, payloads, and response parsing. 검증: worker/API/client 경계가 backtest result를 일관되게 다룬다. -- [ ] [BAS-BOUNDARY-4] `go test ./services/api/...`, `cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart`, `bin/test`, `bin/lint`를 실행한다. 검증: `bin/test`와 `bin/lint`가 통과한다. -- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. - -### [BAS-BOUNDARY-1] Align API parser boundary - -문제: `services/api/internal/contracts/parser_map.go:19` currently registers existing backtest messages. If `01_contract_surface` added list/detail/compare messages and did not register them, API parser tests will not cover the analysis request surface. - -해결 방법: - -Before: - -```go -19 protoSocket.TypeNameOf(&altv1.StartBacktestRequest{}): parserFor(func() proto.Message { return &altv1.StartBacktestRequest{} }), -21 protoSocket.TypeNameOf(&altv1.GetBacktestRunRequest{}): parserFor(func() proto.Message { return &altv1.GetBacktestRunRequest{} }), -23 protoSocket.TypeNameOf(&altv1.GetBacktestResultRequest{}): parserFor(func() proto.Message { return &altv1.GetBacktestResultRequest{} }), -``` - -After: ensure `ListBacktestRuns*`, `GetBacktestRunDetail*`, and `CompareBacktestRuns*` generated messages are present in `ParserMap()` and `TestParserMapIncludesAltMessages`. - -수정 파일 및 체크리스트: - -- [ ] `services/api/internal/contracts/parser_map.go` includes all analysis request/response messages. -- [ ] `services/api/internal/contracts/parser_map_test.go` includes all analysis messages and round-trip parsing. - -테스트 작성: update existing parser map test only. - -중간 검증: - -```bash -go test ./services/api/... -``` - -Expected: exits 0. - -### [BAS-BOUNDARY-2] Add typed client request helpers - -문제: `AltSocketClient.hello()` at `apps/client/lib/src/integrations/socket/alt_socket_client.dart:32` is the only typed helper. Backtest analysis requests would require callers to use raw generated messages directly. - -해결 방법: - -Before: - -```dart -32 Future hello({Duration timeout = const Duration(seconds: 2)}) { -33 return sendRequest( -``` - -After: - -```dart -Future listBacktestRuns( - ListBacktestRunsRequest request, { - Duration timeout = const Duration(seconds: 2), -}) { - return sendRequest( - request, - timeout: timeout, - ); -} -``` - -Add equivalent helpers for `GetBacktestRunDetailRequest`, existing `GetBacktestResultRequest`, and `CompareBacktestRunsRequest`. - -수정 파일 및 체크리스트: - -- [ ] `apps/client/lib/src/integrations/socket/alt_socket_client.dart` imports generated `backtest.pb.dart`. -- [ ] Add `listBacktestRuns`, `getBacktestRunDetail`, `getBacktestResult`, and `compareBacktestRuns` helpers. -- [ ] Keep default timeout consistent with `hello()`. - -테스트 작성: tests in BAS-BOUNDARY-3. - -중간 검증: - -```bash -cd apps/client && flutter analyze --no-fatal-infos -``` - -Expected: exits 0. - -### [BAS-BOUNDARY-3] Test backtest socket helpers - -문제: `apps/client/test/integrations/socket/alt_socket_client_test.dart:113` verifies hello only; no test asserts backtest request type names or response parsing over `PacketBase`. - -해결 방법: Extend fake websocket tests with one helper to assert sent packet type/payload and feed a matching response packet. Cover at least list runs, run detail, get result, and compare runs. - -수정 파일 및 체크리스트: - -- [ ] `apps/client/test/integrations/socket/alt_socket_client_test.dart` imports generated `backtest.pb.dart`. -- [ ] Add tests for each new client helper. -- [ ] Tests assert sent `PacketBase.typeName`, serialized request fields, and parsed response fields. - -테스트 작성: add focused tests in existing `AltSocketClient tests` group. - -중간 검증: - -```bash -cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart -``` - -Expected: exits 0. - -### [BAS-BOUNDARY-4] Boundary verification - -문제: API parser, generated contract, and client helper changes must remain consistent across the workspace. - -해결 방법: run focused API/client checks and full workspace smoke. - -수정 파일 및 체크리스트: - -- [ ] Run API parser tests. -- [ ] Run client socket helper tests. -- [ ] Run full `bin/test`. -- [ ] Run full `bin/lint`. -- [ ] Run `git diff --check`. - -테스트 작성: no extra tests beyond BAS-BOUNDARY-1 and BAS-BOUNDARY-3. - -중간 검증: - -```bash -go test ./services/api/... -cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart -bin/test -bin/lint -git diff --check -``` - -Expected: all commands exit 0. Go test cache output is acceptable after focused API tests pass; Flutter test output must be actual command output. - -## 의존 관계 및 구현 순서 - -Directory dependency `03+01,02_api_client_boundary` means both predecessor task directories must produce `complete.log` before this task starts: - -- `agent-task/m-backtest-analysis-surface/01_contract_surface/complete.log` -- `agent-task/m-backtest-analysis-surface/02+01_worker_analysis_store/complete.log` - -Do not begin if either file is absent. - -## 수정 파일 요약 - -| 파일 | 항목 | -|------|------| -| `services/api/internal/contracts/parser_map.go` | BAS-BOUNDARY-1 | -| `services/api/internal/contracts/parser_map_test.go` | BAS-BOUNDARY-1 | -| `apps/client/lib/src/integrations/socket/alt_socket_client.dart` | BAS-BOUNDARY-2 | -| `apps/client/test/integrations/socket/alt_socket_client_test.dart` | BAS-BOUNDARY-3 | - -## 최종 검증 - -```bash -go test ./services/api/... -cd apps/client && flutter test test/integrations/socket/alt_socket_client_test.dart -bin/test -bin/lint -git diff --check -``` - -Expected: all commands exit 0. Go test cache output is acceptable after focused API tests pass; Flutter test output must be actual command output. - -모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/apps/client/lib/src/integrations/socket/alt_socket_client.dart b/apps/client/lib/src/integrations/socket/alt_socket_client.dart index 679dba7..4b37872 100644 --- a/apps/client/lib/src/integrations/socket/alt_socket_client.dart +++ b/apps/client/lib/src/integrations/socket/alt_socket_client.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:proto_socket/proto_socket.dart'; import 'package:alt_client/src/contracts/alt_contracts.dart'; +import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart'; import 'package:alt_client/src/generated/alt/v1/common.pb.dart'; import 'socket_endpoint.dart'; @@ -38,4 +39,45 @@ class AltSocketClient extends WsProtobufClient { timeout: timeout, ); } + + Future listBacktestRuns( + ListBacktestRunsRequest request, { + Duration timeout = const Duration(seconds: 2), + }) { + return sendRequest( + request, + timeout: timeout, + ); + } + + Future getBacktestRunDetail( + GetBacktestRunDetailRequest request, { + Duration timeout = const Duration(seconds: 2), + }) { + return sendRequest( + request, + timeout: timeout, + ); + } + + Future getBacktestResult( + GetBacktestResultRequest request, { + Duration timeout = const Duration(seconds: 2), + }) { + return sendRequest( + request, + timeout: timeout, + ); + } + + Future compareBacktestRuns( + CompareBacktestRunsRequest request, { + Duration timeout = const Duration(seconds: 2), + }) { + return sendRequest( + request, + timeout: timeout, + ); + } } diff --git a/apps/client/test/integrations/socket/alt_socket_client_test.dart b/apps/client/test/integrations/socket/alt_socket_client_test.dart index d726a28..428eea2 100644 --- a/apps/client/test/integrations/socket/alt_socket_client_test.dart +++ b/apps/client/test/integrations/socket/alt_socket_client_test.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:proto_socket/proto_socket.dart'; +import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart'; import 'package:alt_client/src/generated/alt/v1/common.pb.dart'; import 'package:alt_client/src/integrations/socket/alt_socket_client.dart'; import 'package:alt_client/src/integrations/socket/socket_endpoint.dart'; @@ -147,5 +148,182 @@ void main() { expect(response.serverVersion, equals('1.0.0')); expect(response.altProtocolVersion, equals('alt.v1')); }); + + // Drains the request sent by [trigger], asserts the sent packet type/payload + // through [onSent], then feeds a matching response packet back so the worker + // -> API -> client backtest boundary round-trips consistently. + Future roundTrip({ + required Future Function() trigger, + required void Function(PacketBase sentPacket) onSent, + required String responseTypeName, + required List responseData, + }) async { + final sentCountBefore = fakeWs.sentBytes.length; + unawaited(trigger()); + + await Future.delayed(Duration.zero); + + expect(fakeWs.sentBytes.length, greaterThan(sentCountBefore)); + final sentPacket = PacketBase.fromBuffer(fakeWs.sentBytes.last); + onSent(sentPacket); + + final responsePacket = PacketBase() + ..typeName = responseTypeName + ..nonce = 200 + ..responseNonce = sentPacket.nonce + ..data = responseData; + fakeWs.feedFromServer(responsePacket.writeToBuffer()); + + return sentPacket; + } + + test('listBacktestRuns request-response loop', () async { + final request = ListBacktestRunsRequest() + ..status = BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED; + final expectedResponse = ListBacktestRunsResponse() + ..runs.add(BacktestRun() + ..id = 'run-1' + ..status = BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED); + + final future = client.listBacktestRuns( + request, + timeout: const Duration(seconds: 1), + ); + + await roundTrip( + trigger: () => future, + onSent: (sentPacket) { + expect( + sentPacket.typeName, + equals( + ListBacktestRunsRequest.getDefault().info_.qualifiedMessageName, + ), + ); + final sentRequest = ListBacktestRunsRequest.fromBuffer(sentPacket.data); + expect( + sentRequest.status, + equals(BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED), + ); + }, + responseTypeName: + ListBacktestRunsResponse.getDefault().info_.qualifiedMessageName, + responseData: expectedResponse.writeToBuffer(), + ); + + final response = await future; + expect(response.runs, hasLength(1)); + expect(response.runs.first.id, equals('run-1')); + expect( + response.runs.first.status, + equals(BacktestRunStatus.BACKTEST_RUN_STATUS_SUCCEEDED), + ); + }); + + test('getBacktestRunDetail request-response loop', () async { + final request = GetBacktestRunDetailRequest()..runId = 'run-7'; + final expectedResponse = GetBacktestRunDetailResponse() + ..run = (BacktestRun()..id = 'run-7') + ..result = (BacktestResult()..runId = 'run-7'); + + final future = client.getBacktestRunDetail( + request, + timeout: const Duration(seconds: 1), + ); + + await roundTrip( + trigger: () => future, + onSent: (sentPacket) { + expect( + sentPacket.typeName, + equals( + GetBacktestRunDetailRequest.getDefault() + .info_ + .qualifiedMessageName, + ), + ); + final sentRequest = + GetBacktestRunDetailRequest.fromBuffer(sentPacket.data); + expect(sentRequest.runId, equals('run-7')); + }, + responseTypeName: GetBacktestRunDetailResponse.getDefault() + .info_ + .qualifiedMessageName, + responseData: expectedResponse.writeToBuffer(), + ); + + final response = await future; + expect(response.run.id, equals('run-7')); + expect(response.result.runId, equals('run-7')); + }); + + test('getBacktestResult request-response loop', () async { + final request = GetBacktestResultRequest()..runId = 'run-9'; + final expectedResponse = GetBacktestResultResponse() + ..result = (BacktestResult()..runId = 'run-9'); + + final future = client.getBacktestResult( + request, + timeout: const Duration(seconds: 1), + ); + + await roundTrip( + trigger: () => future, + onSent: (sentPacket) { + expect( + sentPacket.typeName, + equals( + GetBacktestResultRequest.getDefault().info_.qualifiedMessageName, + ), + ); + final sentRequest = + GetBacktestResultRequest.fromBuffer(sentPacket.data); + expect(sentRequest.runId, equals('run-9')); + }, + responseTypeName: + GetBacktestResultResponse.getDefault().info_.qualifiedMessageName, + responseData: expectedResponse.writeToBuffer(), + ); + + final response = await future; + expect(response.result.runId, equals('run-9')); + }); + + test('compareBacktestRuns request-response loop', () async { + final request = CompareBacktestRunsRequest() + ..runIds.addAll(['run-1', 'run-2']); + final expectedResponse = CompareBacktestRunsResponse() + ..results.add(BacktestResult()..runId = 'run-1') + ..results.add(BacktestResult()..runId = 'run-2'); + + final future = client.compareBacktestRuns( + request, + timeout: const Duration(seconds: 1), + ); + + await roundTrip( + trigger: () => future, + onSent: (sentPacket) { + expect( + sentPacket.typeName, + equals( + CompareBacktestRunsRequest.getDefault().info_.qualifiedMessageName, + ), + ); + final sentRequest = + CompareBacktestRunsRequest.fromBuffer(sentPacket.data); + expect(sentRequest.runIds, equals(['run-1', 'run-2'])); + }, + responseTypeName: + CompareBacktestRunsResponse.getDefault().info_.qualifiedMessageName, + responseData: expectedResponse.writeToBuffer(), + ); + + final response = await future; + expect(response.results, hasLength(2)); + expect( + response.results.map((r) => r.runId), + equals(['run-1', 'run-2']), + ); + }); }); }