feat: move resale dashboard into marketplace
This commit is contained in:
58
tests/unit/buyer-resale-dashboard.test.ts
Normal file
58
tests/unit/buyer-resale-dashboard.test.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import request from "supertest";
|
||||
import { createApp } from "../../buyer/src/server.js";
|
||||
|
||||
describe("buyer resale dashboard proxy", () => {
|
||||
it("serves the private supplier snapshot from a same-origin buyer route", async () => {
|
||||
const payload = {
|
||||
generatedAt: "2026-08-28T12:00:00.000Z",
|
||||
controller: { ok: true, phase: "active" },
|
||||
surplus: { earnings: { requests: 1_333 } },
|
||||
vector: { status: "retired" },
|
||||
};
|
||||
const fetchImpl = vi.fn(async (_url: string | URL | Request, init?: RequestInit) => {
|
||||
expect(new Headers(init?.headers).get("accept")).toBe("application/json");
|
||||
expect(init?.redirect).toBe("error");
|
||||
return Response.json(payload);
|
||||
});
|
||||
const app = createApp({
|
||||
resaleDashboardUrl:
|
||||
"http://marketplace-mainnet-surplus-seller:8080/internal/resale-dashboard/",
|
||||
fetchImpl,
|
||||
});
|
||||
|
||||
const response = await request(app).get("/v1/resale-dashboard");
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body).toEqual(payload);
|
||||
expect(response.headers["cache-control"]).toBe("no-store");
|
||||
expect(fetchImpl).toHaveBeenCalledWith(
|
||||
"http://marketplace-mainnet-surplus-seller:8080/internal/resale-dashboard",
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("fails without exposing an upstream error body", async () => {
|
||||
const app = createApp({
|
||||
resaleDashboardUrl: "http://seller/internal/resale-dashboard",
|
||||
fetchImpl: async () => new Response("secret upstream detail", { status: 500 }),
|
||||
});
|
||||
|
||||
const response = await request(app).get("/v1/resale-dashboard");
|
||||
|
||||
expect(response.status).toBe(502);
|
||||
expect(response.body.error).toBe("dashboard_upstream_error");
|
||||
expect(JSON.stringify(response.body)).not.toContain("secret upstream detail");
|
||||
});
|
||||
|
||||
it("returns a bounded feature-disabled response when unconfigured", async () => {
|
||||
const fetchImpl = vi.fn();
|
||||
const app = createApp({ fetchImpl });
|
||||
|
||||
const response = await request(app).get("/v1/resale-dashboard");
|
||||
|
||||
expect(response.status).toBe(503);
|
||||
expect(response.body.error).toBe("service_unavailable");
|
||||
expect(fetchImpl).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -100,6 +100,24 @@ describe("loadConfig(env)", () => {
|
||||
it("throws when PDF_ENABLED is not 0 or 1", () => {
|
||||
expect(() => loadConfig({ ...VALID_ENV, PDF_ENABLED: "false" })).toThrow(/PDF_ENABLED/);
|
||||
});
|
||||
|
||||
it("normalizes the private resale dashboard URL", () => {
|
||||
const config = loadConfig({
|
||||
...VALID_ENV,
|
||||
SURPLUS_DASHBOARD_URL:
|
||||
"http://surplus-seller:8080/internal/resale-dashboard/",
|
||||
});
|
||||
expect(config.resaleDashboardUrl).toBe(
|
||||
"http://surplus-seller:8080/internal/resale-dashboard",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a resale dashboard URL with an unsupported protocol", () => {
|
||||
expect(() => loadConfig({
|
||||
...VALID_ENV,
|
||||
SURPLUS_DASHBOARD_URL: "file:///tmp/dashboard.json",
|
||||
})).toThrow(/SURPLUS_DASHBOARD_URL/);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── createApp tests ──────────────────────────────────────────────────────────
|
||||
|
||||
107
tests/unit/buyer-ui-resale-dashboard.test.tsx
Normal file
107
tests/unit/buyer-ui-resale-dashboard.test.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
// @vitest-environment happy-dom
|
||||
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import ResaleDashboard from "../../buyer/src/ui/pages/ResaleDashboard.js";
|
||||
|
||||
const DASHBOARD = {
|
||||
generatedAt: "2026-08-28T12:00:00.000Z",
|
||||
controller: { ok: true, phase: "active", lastError: null },
|
||||
identity: {
|
||||
sellerWallet: "0x1111111111111111111111111111111111111111",
|
||||
payoutAddress: "0x1111111111111111111111111111111111111111",
|
||||
},
|
||||
provider: {
|
||||
hardLimitUsd: "20.000000",
|
||||
remainingUsd: "15.157733",
|
||||
usedUsd: "4.842267",
|
||||
reserveUsd: "1.00",
|
||||
estimatedDailyExposureUsd: "1.020408",
|
||||
},
|
||||
surplus: {
|
||||
offer: {
|
||||
id: "offer-1",
|
||||
model: "deepseek-v4-flash",
|
||||
providerModel: "deepseek/deepseek-v4-flash",
|
||||
status: "active",
|
||||
dailyCapUsd: "0.050000",
|
||||
costMultiplier: 0.05065,
|
||||
inputUsdPer1m: "0.004027",
|
||||
outputUsdPer1m: "0.008055",
|
||||
rank: 11,
|
||||
available: true,
|
||||
healthy: true,
|
||||
trusted: true,
|
||||
trades24h: 654,
|
||||
volume24hUsd: "0.112434",
|
||||
capRemainingUsd: "0.000000",
|
||||
},
|
||||
earnings: {
|
||||
totalUsd: "0.239903",
|
||||
pendingUsd: "0.219945",
|
||||
paidUsd: "0.019958",
|
||||
requests: 1_333,
|
||||
tokens: 138_612_442,
|
||||
today: { earnedUsd: 0.053093, requests: 240, totalTokens: 33_913_537 },
|
||||
topModel: "deepseek-v4-flash",
|
||||
payoutHoldReason: "new_seller",
|
||||
payoutHoldReleasesAt: "2026-08-29T11:24:42.552Z",
|
||||
},
|
||||
recentSales: [{
|
||||
id: "sale-1",
|
||||
model: "deepseek-v4-flash",
|
||||
createdAt: "2026-08-28T11:00:00.000Z",
|
||||
settlementStatus: "confirmed",
|
||||
inputTokens: 10,
|
||||
outputTokens: 2,
|
||||
cacheReadTokens: 3,
|
||||
revenueUsd: "0.000083",
|
||||
transactionHash: "0x" + "c".repeat(64),
|
||||
}],
|
||||
},
|
||||
vector: {
|
||||
status: "retired",
|
||||
model: "deepseek/deepseek-v4-flash",
|
||||
supplierWallet: "addr1test",
|
||||
advertRef: `${"a".repeat(64)}#0`,
|
||||
retirementTransaction: "b".repeat(64),
|
||||
retiredOn: "2026-08-27",
|
||||
historicalAp3xEarned: "1.600000",
|
||||
historicalSettledJobs: 8,
|
||||
historicalUpstreamSpendUsd: "0.000093",
|
||||
},
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("ResaleDashboard", () => {
|
||||
it("matches the marketplace card system and renders both settlement markets", async () => {
|
||||
const fetchMock = vi.fn(async () => Response.json(DASHBOARD));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
render(<ResaleDashboard />);
|
||||
|
||||
expect(await screen.findByRole("heading", { name: "Resale Dashboard" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Surplus Intelligence" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Vector marketplace" })).toBeInTheDocument();
|
||||
expect(screen.getByText("$0.2399")).toBeInTheDocument();
|
||||
expect(screen.getByText("1.600000 AP3X")).toBeInTheDocument();
|
||||
expect(screen.getByText("confirmed")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("resale-dashboard")).toHaveClass("max-w-7xl");
|
||||
expect(document.querySelectorAll(".bg-white").length).toBeGreaterThan(3);
|
||||
expect(screen.getByRole("link", { name: /Open confirmed transaction/ })).toHaveAttribute(
|
||||
"href",
|
||||
`https://vector.apexscan.org/en/transaction/${"b".repeat(64)}/summary/`,
|
||||
);
|
||||
expect(screen.getByRole("link", { name: /0xcccc/ })).toHaveAttribute(
|
||||
"href",
|
||||
`https://basescan.org/tx/0x${"c".repeat(64)}`,
|
||||
);
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledWith(
|
||||
"/v1/resale-dashboard",
|
||||
{ cache: "no-store" },
|
||||
));
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
SurplusDashboardService,
|
||||
type SurplusDashboardSnapshot,
|
||||
} from "../../supplier/src/surplus/dashboard.js";
|
||||
import { renderSurplusDashboardPage } from "../../supplier/src/surplus/dashboardPage.js";
|
||||
import { createSurplusServer } from "../../supplier/src/cli/surplus-seller.js";
|
||||
import type { SurplusControllerStatus } from "../../supplier/src/surplus/controller.js";
|
||||
|
||||
@@ -222,38 +221,21 @@ describe("SurplusDashboardService", () => {
|
||||
expect(getOrderBook).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("unified dashboard HTTP surface", () => {
|
||||
it("serves the dashboard, cached API data, and security headers", async () => {
|
||||
describe("private supplier dashboard data surface", () => {
|
||||
it("serves redacted data internally without hosting a dashboard page", async () => {
|
||||
const data = snapshot();
|
||||
const server = createSurplusServer(
|
||||
{ snapshot: () => STATUS, healthy: () => true },
|
||||
{ getSnapshot: async () => data },
|
||||
);
|
||||
|
||||
const page = await request(server).get("/reseller");
|
||||
expect(page.status).toBe(200);
|
||||
expect(page.text).toContain("One capacity pool.");
|
||||
expect(page.text).toContain("Vector marketplace");
|
||||
expect(page.headers["content-security-policy"]).toContain("frame-ancestors 'none'");
|
||||
|
||||
const api = await request(server).get("/dashboard/api");
|
||||
const api = await request(server).get("/internal/resale-dashboard");
|
||||
expect(api.status).toBe(200);
|
||||
expect(api.body).toEqual(data);
|
||||
expect(JSON.stringify(api.body)).not.toContain("test-secret");
|
||||
|
||||
const root = await request(server).get("/");
|
||||
expect(root.status).toBe(302);
|
||||
expect(root.headers.location).toBe("/reseller");
|
||||
expect((await request(server).get("/reseller")).status).toBe(404);
|
||||
expect((await request(server).get("/")).status).toBe(404);
|
||||
server.close();
|
||||
});
|
||||
|
||||
it("renders static HTML without embedding credentials", () => {
|
||||
const html = renderSurplusDashboardPage();
|
||||
expect(html).toContain("/dashboard/api");
|
||||
expect(html).toContain("Surplus Intelligence");
|
||||
expect(html).toContain("Vector marketplace");
|
||||
expect(html).not.toContain("si_seller_");
|
||||
expect(html).not.toContain("sk-or-v1-");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user