292 lines
8.7 KiB
TypeScript
292 lines
8.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import type { SurplusManagerConfig } from "../../supplier/src/surplus/config.js";
|
|
import {
|
|
SurplusSellerController,
|
|
type SurplusControllerClient,
|
|
} from "../../supplier/src/surplus/controller.js";
|
|
import {
|
|
SurplusHttpError,
|
|
type SurplusDiscoveredModel,
|
|
type SurplusOffer,
|
|
type SurplusOfferPatch,
|
|
type SurplusOfferWrite,
|
|
} from "../../supplier/src/surplus/client.js";
|
|
import type { OpenRouterKeyAllowance } from "../../supplier/src/surplus/openRouterKey.js";
|
|
import type {
|
|
SurplusControllerState,
|
|
SurplusStateStore,
|
|
} from "../../supplier/src/surplus/state.js";
|
|
|
|
const SELLER_WALLET = "0x1111111111111111111111111111111111111111";
|
|
|
|
function config(): SurplusManagerConfig {
|
|
return {
|
|
live: true,
|
|
apiBaseUrl: "https://api.surplusintelligence.ai",
|
|
sellerApiKey: "si_seller_test",
|
|
providerApiKey: "sk-or-v1-test",
|
|
providerBaseUrl: "https://openrouter.ai/api/v1",
|
|
capacityBaseUrl: "https://openrouter.ai/api",
|
|
perOfferCapUsd: "1.00",
|
|
aggregateCapUsd: "1.00",
|
|
sellerWallet: SELLER_WALLET,
|
|
payoutAddress: SELLER_WALLET,
|
|
maxCandidateOrderBooks: 10,
|
|
reserveUsd: "1.00",
|
|
maxProviderLimitUsd: "20.00",
|
|
recoveryBps: 490,
|
|
undercutBps: 10,
|
|
pollIntervalMs: 300_000,
|
|
requestTimeoutMs: 120_000,
|
|
stopAfterTrades: 1,
|
|
settledStatuses: ["confirmed"],
|
|
statePath: "/tmp/surplus-test-state.json",
|
|
port: 8080,
|
|
};
|
|
}
|
|
|
|
class MemoryStateStore implements SurplusStateStore {
|
|
state: SurplusControllerState = { version: 1, phase: "selecting" };
|
|
|
|
async load(): Promise<SurplusControllerState> {
|
|
return structuredClone(this.state);
|
|
}
|
|
|
|
async save(state: SurplusControllerState): Promise<void> {
|
|
this.state = structuredClone(state);
|
|
}
|
|
}
|
|
|
|
class FakeSurplusClient implements SurplusControllerClient {
|
|
offer: SurplusOffer | null = null;
|
|
trades24h = 0;
|
|
settled = false;
|
|
createCalls = 0;
|
|
pauseCalls = 0;
|
|
externalCalls = 0;
|
|
createError: Error | null = null;
|
|
discoveryError: Error | null = null;
|
|
createdPayload: SurplusOfferWrite | null = null;
|
|
patchPayload: SurplusOfferPatch | null = null;
|
|
|
|
async listAllOffers(): Promise<SurplusOffer[]> {
|
|
this.externalCalls += 1;
|
|
return this.offer ? [{ ...this.offer }] : [];
|
|
}
|
|
|
|
async discoverModels(): Promise<SurplusDiscoveredModel[]> {
|
|
this.externalCalls += 1;
|
|
if (this.discoveryError) throw this.discoveryError;
|
|
return [{
|
|
model: "beta-model",
|
|
providerModelId: "vendor/beta-model",
|
|
supported: true,
|
|
inputUsdPer1m: 0.07952,
|
|
outputUsdPer1m: 0.15904,
|
|
priceUnit: "M",
|
|
priceVariable: false,
|
|
modelType: "text",
|
|
availabilityStatus: "available",
|
|
}];
|
|
}
|
|
|
|
async getMarkets() {
|
|
this.externalCalls += 1;
|
|
return [{
|
|
model: "beta-model",
|
|
requests24h: 100,
|
|
volume24h: 1_000,
|
|
bestInputMicroUsdPer1m: 3_976,
|
|
bestOutputMicroUsdPer1m: 7_952,
|
|
healthySellerCount: 1,
|
|
}];
|
|
}
|
|
|
|
async getOrderBook(model: string) {
|
|
this.externalCalls += 1;
|
|
const offers = [{
|
|
id: "external",
|
|
seller: "0x2222222222222222222222222222222222222222",
|
|
sellerBaseUrl: "https://openrouter.ai/api/v1",
|
|
inputMicroUsdPer1m: 3_976,
|
|
outputMicroUsdPer1m: 7_952,
|
|
available: true,
|
|
healthy: true,
|
|
trusted: true,
|
|
trades24h: 0,
|
|
}];
|
|
if (this.offer) {
|
|
offers.push({
|
|
id: this.offer.id,
|
|
seller: SELLER_WALLET,
|
|
sellerBaseUrl: this.offer.sellerBaseUrl,
|
|
inputMicroUsdPer1m: this.offer.inputMicroUsdPer1m ?? 3_972,
|
|
outputMicroUsdPer1m: this.offer.outputMicroUsdPer1m ?? 7_944,
|
|
available: this.offer.status === "active",
|
|
healthy: true,
|
|
trusted: true,
|
|
trades24h: this.trades24h,
|
|
});
|
|
}
|
|
return { model, offers };
|
|
}
|
|
|
|
async testConnection(): Promise<number> {
|
|
this.externalCalls += 1;
|
|
return 10;
|
|
}
|
|
|
|
async createOffer(input: SurplusOfferWrite): Promise<string> {
|
|
this.externalCalls += 1;
|
|
this.createCalls += 1;
|
|
this.createdPayload = input;
|
|
if (this.createError) throw this.createError;
|
|
this.offer = {
|
|
id: "managed-offer",
|
|
model: input.model,
|
|
sellerBaseUrl: input.sellerBaseUrl,
|
|
status: "active",
|
|
capDailyUsd: input.dailyCapUsd,
|
|
costMultiplierPpm: Math.round(input.costMultiplier * 1_000_000),
|
|
inputMicroUsdPer1m: Math.floor(79_520 * input.costMultiplier),
|
|
outputMicroUsdPer1m: Math.floor(159_040 * input.costMultiplier),
|
|
};
|
|
return this.offer.id;
|
|
}
|
|
|
|
async updateOffer(_id: string, patch: SurplusOfferPatch): Promise<void> {
|
|
this.externalCalls += 1;
|
|
this.patchPayload = patch;
|
|
if (!this.offer) throw new Error("missing fake offer");
|
|
this.offer.costMultiplierPpm = Math.round(
|
|
patch.costMultiplier * 1_000_000,
|
|
);
|
|
this.offer.inputMicroUsdPer1m = Math.floor(
|
|
79_520 * patch.costMultiplier,
|
|
);
|
|
this.offer.outputMicroUsdPer1m = Math.floor(
|
|
159_040 * patch.costMultiplier,
|
|
);
|
|
this.offer.capDailyUsd = patch.dailyCapUsd;
|
|
}
|
|
|
|
async pauseOffer(): Promise<void> {
|
|
this.externalCalls += 1;
|
|
this.pauseCalls += 1;
|
|
if (this.offer) this.offer.status = "inactive";
|
|
}
|
|
|
|
async resumeOffer(): Promise<void> {
|
|
this.externalCalls += 1;
|
|
if (this.offer) this.offer.status = "active";
|
|
}
|
|
|
|
async getEarnings() {
|
|
this.externalCalls += 1;
|
|
return {
|
|
totalEarnedMicroUsd: this.settled ? 100 : 0,
|
|
pendingMicroUsd: 0,
|
|
paidMicroUsd: this.settled ? 100 : 0,
|
|
recentSales: this.settled ? [{
|
|
model: "beta-model",
|
|
offerId: "managed-offer",
|
|
settlementStatus: "confirmed",
|
|
createdAt: "2026-08-27T10:00:01.000Z",
|
|
sellerCostMicroUsd: 100,
|
|
}] : [],
|
|
};
|
|
}
|
|
}
|
|
|
|
const ALLOWANCE: OpenRouterKeyAllowance = {
|
|
limitUsdNanos: 20_000_000_000n,
|
|
remainingUsdNanos: 20_000_000_000n,
|
|
reset: null,
|
|
checkedAtMs: Date.parse("2026-08-27T10:00:00.000Z"),
|
|
};
|
|
|
|
function controller(
|
|
client: FakeSurplusClient,
|
|
store: MemoryStateStore,
|
|
): SurplusSellerController {
|
|
let mutation = 0;
|
|
return new SurplusSellerController({
|
|
config: config(),
|
|
client,
|
|
allowance: { readAllowance: async () => ALLOWANCE },
|
|
stateStore: store,
|
|
now: () => Date.parse("2026-08-27T10:00:00.000Z"),
|
|
mutationId: () => `mutation-${++mutation}`,
|
|
});
|
|
}
|
|
|
|
describe("SurplusSellerController", () => {
|
|
it("selects a discovered model, creates one bounded offer, and stops after settlement", async () => {
|
|
const client = new FakeSurplusClient();
|
|
const store = new MemoryStateStore();
|
|
const activeController = controller(client, store);
|
|
|
|
await activeController.runOnce();
|
|
|
|
expect(client.createCalls).toBe(1);
|
|
expect(client.createdPayload).toMatchObject({
|
|
model: "beta-model",
|
|
costMultiplier: 0.04995,
|
|
dailyCapUsd: 1,
|
|
payoutAddress: SELLER_WALLET,
|
|
});
|
|
expect(store.state.phase).toBe("active");
|
|
expect(activeController.snapshot()).toMatchObject({
|
|
ok: true,
|
|
phase: "active",
|
|
model: "beta-model",
|
|
providerModelId: "vendor/beta-model",
|
|
});
|
|
|
|
client.trades24h = 1;
|
|
client.settled = true;
|
|
await activeController.runOnce();
|
|
|
|
expect(client.pauseCalls).toBeGreaterThan(0);
|
|
expect(client.offer?.status).toBe("inactive");
|
|
expect(store.state.phase).toBe("completed");
|
|
expect(activeController.snapshot()).toMatchObject({ ok: true, phase: "completed" });
|
|
|
|
const callsBeforeRestart = client.externalCalls;
|
|
const restarted = controller(client, store);
|
|
await restarted.start();
|
|
expect(client.externalCalls).toBe(callsBeforeRestart);
|
|
expect(restarted.snapshot()).toMatchObject({ ok: true, phase: "completed" });
|
|
await restarted.stop();
|
|
});
|
|
|
|
it("never retries an ambiguous create when no matching offer is visible", async () => {
|
|
const client = new FakeSurplusClient();
|
|
client.createError = new SurplusHttpError("network lost", null);
|
|
const store = new MemoryStateStore();
|
|
const activeController = controller(client, store);
|
|
|
|
await activeController.runOnce();
|
|
expect(client.createCalls).toBe(1);
|
|
expect(store.state.phase).toBe("create_pending");
|
|
|
|
await activeController.runOnce();
|
|
expect(client.createCalls).toBe(1);
|
|
expect(activeController.snapshot()).toMatchObject({ ok: false, phase: "error" });
|
|
});
|
|
|
|
it("pauses the managed offer when a required pricing snapshot fails", async () => {
|
|
const client = new FakeSurplusClient();
|
|
const store = new MemoryStateStore();
|
|
const activeController = controller(client, store);
|
|
await activeController.runOnce();
|
|
expect(client.offer?.status).toBe("active");
|
|
|
|
client.discoveryError = new Error("discovery unavailable");
|
|
await activeController.runOnce();
|
|
|
|
expect(client.offer?.status).toBe("inactive");
|
|
expect(activeController.snapshot()).toMatchObject({ ok: false, phase: "error" });
|
|
});
|
|
});
|