feat: bootstrap auto virtual tryon admin frontend
This commit is contained in:
97
tests/lib/adapters/orders.test.ts
Normal file
97
tests/lib/adapters/orders.test.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { adaptOrderDetail, adaptOrderSummary } from "@/lib/adapters/orders";
|
||||
import type { AssetDto, OrderDetailResponseDto } from "@/lib/types/backend";
|
||||
|
||||
const ORDER_BASE: OrderDetailResponseDto = {
|
||||
order_id: 101,
|
||||
customer_level: "mid",
|
||||
service_mode: "semi_pro",
|
||||
status: "waiting_review",
|
||||
model_id: 7,
|
||||
pose_id: 8,
|
||||
garment_asset_id: 9,
|
||||
scene_ref_asset_id: 10,
|
||||
final_asset_id: null,
|
||||
workflow_id: "wf-101",
|
||||
current_step: "review",
|
||||
current_revision_asset_id: null,
|
||||
current_revision_version: null,
|
||||
latest_revision_asset_id: null,
|
||||
latest_revision_version: null,
|
||||
revision_count: 0,
|
||||
review_task_status: null,
|
||||
pending_manual_confirm: false,
|
||||
final_asset: null,
|
||||
created_at: "2026-03-27T00:00:00Z",
|
||||
updated_at: "2026-03-27T00:10:00Z",
|
||||
};
|
||||
|
||||
const ASSET_BASE: AssetDto = {
|
||||
id: 11,
|
||||
order_id: 101,
|
||||
asset_type: "final",
|
||||
step_name: "export",
|
||||
parent_asset_id: null,
|
||||
root_asset_id: null,
|
||||
version_no: 0,
|
||||
is_current_version: false,
|
||||
uri: "https://cdn.example.com/final-11.png",
|
||||
metadata_json: null,
|
||||
created_at: "2026-03-27T00:09:00Z",
|
||||
};
|
||||
|
||||
test("marks mock asset uris as mock previews", () => {
|
||||
const viewModel = adaptOrderDetail({
|
||||
...ORDER_BASE,
|
||||
final_asset_id: 11,
|
||||
final_asset: {
|
||||
...ASSET_BASE,
|
||||
uri: "mock://result-11",
|
||||
},
|
||||
});
|
||||
|
||||
expect(viewModel.finalAsset?.isMock).toBe(true);
|
||||
expect(viewModel.hasMockAssets).toBe(true);
|
||||
});
|
||||
|
||||
test("keeps missing final assets and empty asset lists as business-empty states", () => {
|
||||
const viewModel = adaptOrderDetail(ORDER_BASE, []);
|
||||
|
||||
expect(viewModel.finalAsset).toBeNull();
|
||||
expect(viewModel.finalAssetState.kind).toBe("business-empty");
|
||||
expect(viewModel.assets).toEqual([]);
|
||||
expect(viewModel.assetGalleryState.kind).toBe("business-empty");
|
||||
});
|
||||
|
||||
test("maps order summary status metadata and current step labels", () => {
|
||||
const viewModel = adaptOrderSummary(ORDER_BASE);
|
||||
|
||||
expect(viewModel).toMatchObject({
|
||||
orderId: 101,
|
||||
workflowId: "wf-101",
|
||||
status: "waiting_review",
|
||||
currentStep: "review",
|
||||
currentStepLabel: "人工审核",
|
||||
statusMeta: {
|
||||
label: "待审核",
|
||||
tone: "warning",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("maps key order detail labels and status metadata", () => {
|
||||
const viewModel = adaptOrderDetail(
|
||||
{
|
||||
...ORDER_BASE,
|
||||
status: "failed",
|
||||
current_step: "fusion",
|
||||
},
|
||||
[ASSET_BASE],
|
||||
);
|
||||
|
||||
expect(viewModel.statusMeta).toEqual({
|
||||
label: "失败",
|
||||
tone: "danger",
|
||||
});
|
||||
expect(viewModel.currentStepLabel).toBe("融合");
|
||||
expect(viewModel.assetGalleryState.kind).toBe("ready");
|
||||
});
|
||||
59
tests/lib/adapters/reviews.test.ts
Normal file
59
tests/lib/adapters/reviews.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { adaptPendingReviews, adaptReviewSubmission } from "@/lib/adapters/reviews";
|
||||
import type {
|
||||
PendingReviewResponseDto,
|
||||
SubmitReviewResponseDto,
|
||||
} from "@/lib/types/backend";
|
||||
|
||||
const PENDING_REVIEW_BASE: PendingReviewResponseDto = {
|
||||
review_task_id: 301,
|
||||
order_id: 101,
|
||||
workflow_id: "wf-101",
|
||||
current_step: "review",
|
||||
review_task_status: "pending",
|
||||
latest_revision_asset_id: null,
|
||||
current_revision_asset_id: null,
|
||||
latest_revision_version: null,
|
||||
revision_count: 0,
|
||||
pending_manual_confirm: false,
|
||||
created_at: "2026-03-27T00:00:00Z",
|
||||
};
|
||||
|
||||
const REVIEW_SUBMISSION_BASE: SubmitReviewResponseDto = {
|
||||
order_id: 101,
|
||||
workflow_id: "wf-101",
|
||||
decision: "rerun_scene",
|
||||
status: "queued",
|
||||
};
|
||||
|
||||
test("returns a business-empty queue state when no reviews are pending", () => {
|
||||
const viewModel = adaptPendingReviews([]);
|
||||
|
||||
expect(viewModel.items).toEqual([]);
|
||||
expect(viewModel.state.kind).toBe("business-empty");
|
||||
});
|
||||
|
||||
test("maps pending reviews to waiting-review queue items", () => {
|
||||
const viewModel = adaptPendingReviews([PENDING_REVIEW_BASE]);
|
||||
|
||||
expect(viewModel.state.kind).toBe("ready");
|
||||
expect(viewModel.items[0]).toMatchObject({
|
||||
orderId: 101,
|
||||
reviewTaskId: 301,
|
||||
status: "waiting_review",
|
||||
});
|
||||
});
|
||||
|
||||
test("maps review submission decision metadata", () => {
|
||||
const viewModel = adaptReviewSubmission(REVIEW_SUBMISSION_BASE);
|
||||
|
||||
expect(viewModel).toMatchObject({
|
||||
orderId: 101,
|
||||
workflowId: "wf-101",
|
||||
decision: "rerun_scene",
|
||||
status: "queued",
|
||||
decisionMeta: {
|
||||
label: "重跑场景",
|
||||
tone: "warning",
|
||||
},
|
||||
});
|
||||
});
|
||||
87
tests/lib/adapters/workflows.test.ts
Normal file
87
tests/lib/adapters/workflows.test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import {
|
||||
adaptWorkflowDetail,
|
||||
adaptWorkflowLookupItem,
|
||||
} from "@/lib/adapters/workflows";
|
||||
import type { WorkflowStatusResponseDto } from "@/lib/types/backend";
|
||||
|
||||
const WORKFLOW_BASE: WorkflowStatusResponseDto = {
|
||||
order_id: 101,
|
||||
workflow_id: "wf-101",
|
||||
workflow_type: "mid_end",
|
||||
workflow_status: "running",
|
||||
current_step: "fusion",
|
||||
current_revision_asset_id: null,
|
||||
current_revision_version: null,
|
||||
latest_revision_asset_id: null,
|
||||
latest_revision_version: null,
|
||||
revision_count: 0,
|
||||
review_task_status: null,
|
||||
pending_manual_confirm: false,
|
||||
steps: [],
|
||||
created_at: "2026-03-27T00:00:00Z",
|
||||
updated_at: "2026-03-27T00:10:00Z",
|
||||
};
|
||||
|
||||
test("keeps an empty workflow timeline as a business-empty state", () => {
|
||||
const viewModel = adaptWorkflowDetail(WORKFLOW_BASE);
|
||||
|
||||
expect(viewModel.steps).toEqual([]);
|
||||
expect(viewModel.stepTimelineState.kind).toBe("business-empty");
|
||||
});
|
||||
|
||||
test("tags nested mock asset uris found in workflow step payloads", () => {
|
||||
const viewModel = adaptWorkflowDetail({
|
||||
...WORKFLOW_BASE,
|
||||
steps: [
|
||||
{
|
||||
id: 1,
|
||||
workflow_run_id: 9001,
|
||||
step_name: "fusion",
|
||||
step_status: "failed",
|
||||
input_json: null,
|
||||
output_json: {
|
||||
preview_uri: "mock://fusion-preview-1",
|
||||
nested: {
|
||||
asset_uri: "mock://fusion-asset-1",
|
||||
duplicate_asset_uri: "mock://fusion-asset-1",
|
||||
},
|
||||
ignored_text: "mock://should-not-be-collected",
|
||||
},
|
||||
error_message: "fusion failed",
|
||||
started_at: "2026-03-27T00:08:00Z",
|
||||
ended_at: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(viewModel.statusMeta).toEqual({
|
||||
label: "处理中",
|
||||
tone: "info",
|
||||
});
|
||||
expect(viewModel.currentStepLabel).toBe("融合");
|
||||
expect(viewModel.steps[0].containsMockAssets).toBe(true);
|
||||
expect(viewModel.steps[0].isCurrent).toBe(true);
|
||||
expect(viewModel.steps[0].isFailed).toBe(true);
|
||||
expect(viewModel.steps[0].mockAssetUris).toEqual([
|
||||
"mock://fusion-preview-1",
|
||||
"mock://fusion-asset-1",
|
||||
]);
|
||||
expect(viewModel.failureCount).toBe(1);
|
||||
});
|
||||
|
||||
test("maps workflow lookup status and current step labels", () => {
|
||||
const viewModel = adaptWorkflowLookupItem(WORKFLOW_BASE);
|
||||
|
||||
expect(viewModel).toMatchObject({
|
||||
orderId: 101,
|
||||
workflowId: "wf-101",
|
||||
workflowType: "mid_end",
|
||||
status: "running",
|
||||
currentStep: "fusion",
|
||||
currentStepLabel: "融合",
|
||||
statusMeta: {
|
||||
label: "处理中",
|
||||
tone: "info",
|
||||
},
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user