Add store header OCR and market observation outbox
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { forwardMarketBatch } from "../vite.config.js";
|
||||
|
||||
test("keeps the import token in the local server-side forwarder", async () => {
|
||||
let forwarded;
|
||||
const response = await forwardMarketBatch('{"batchId":"one"}', {
|
||||
url: "https://l2.example/api/l2/market/import",
|
||||
token: "secret",
|
||||
fetchImpl: async (url, init) => {
|
||||
forwarded = { url, init };
|
||||
return Response.json({ imported: true });
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(response.status, 200);
|
||||
assert.equal(forwarded.url, "https://l2.example/api/l2/market/import");
|
||||
assert.equal(forwarded.init.headers.Authorization, "Bearer secret");
|
||||
assert.equal(forwarded.init.body, '{"batchId":"one"}');
|
||||
assert.equal((await forwardMarketBatch("{}", {})).status, 503);
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { MarketOutbox } from "../src/market-outbox.js";
|
||||
|
||||
test("keeps the same batch across retries and deletes only after acknowledgement", async () => {
|
||||
const store = memoryStore();
|
||||
await store.add({ observationId: "one", name: "Stem" });
|
||||
await store.add({ observationId: "two", name: "Stem" });
|
||||
const sent = [];
|
||||
let fail = true;
|
||||
const send = async (batch) => {
|
||||
sent.push(batch);
|
||||
if (fail) throw new Error("offline");
|
||||
};
|
||||
const options = { now: () => "2026-08-11T12:00:00.000Z", uuid: () => "batch-one" };
|
||||
|
||||
await assert.rejects(new MarketOutbox(store, send, options).flush(), /offline/);
|
||||
fail = false;
|
||||
const result = await new MarketOutbox(store, send, options).flush();
|
||||
|
||||
assert.equal(result.count, 2);
|
||||
assert.equal(sent[0].batchId, "batch-one");
|
||||
assert.deepEqual(sent[1], sent[0]);
|
||||
assert.deepEqual(await store.list(200), []);
|
||||
});
|
||||
|
||||
function memoryStore() {
|
||||
const observations = new Map();
|
||||
let batch = null;
|
||||
return {
|
||||
async add(value) {
|
||||
observations.set(value.observationId, value);
|
||||
},
|
||||
async list(limit) {
|
||||
return [...observations.values()].slice(0, limit);
|
||||
},
|
||||
async getMany(ids) {
|
||||
return ids.map((id) => observations.get(id)).filter(Boolean);
|
||||
},
|
||||
async getBatch() {
|
||||
return batch;
|
||||
},
|
||||
async setBatch(value) {
|
||||
batch = value;
|
||||
},
|
||||
async ack(value) {
|
||||
if (batch?.batchId !== value.batchId) return;
|
||||
value.observationIds.forEach((id) => observations.delete(id));
|
||||
batch = null;
|
||||
},
|
||||
};
|
||||
}
|
||||
+14
-16
@@ -3,12 +3,11 @@ import assert from "node:assert/strict";
|
||||
import {
|
||||
isCalibrationReady,
|
||||
isRectInside,
|
||||
parseMerchantText,
|
||||
parseFieldValue,
|
||||
parseStoreHeaderText,
|
||||
parseTooltipText,
|
||||
rectFromPoints,
|
||||
resolveFieldRect,
|
||||
resolveScreenRect,
|
||||
} from "../src/parser.js";
|
||||
|
||||
test("normalizes a reverse drag into a positive rectangle", () => {
|
||||
@@ -41,7 +40,7 @@ test("requires both anchors and exact OCR regions", () => {
|
||||
assert.equal(
|
||||
isCalibrationReady({
|
||||
saleAnchor: { image: "data:image/png;base64,x", width: 20, height: 10 },
|
||||
merchantRegion: { xRatio: 0.1, yRatio: 0.1, widthRatio: 0.2, heightRatio: 0.1 },
|
||||
storeHeaderRegion: { offsetX: -50, offsetY: -300, width: 250, height: 24 },
|
||||
tooltipSearchRegion: { offsetX: -100, offsetY: -300, width: 300, height: 100 },
|
||||
tooltipAnchor: { image: "data:image/png;base64,y", width: 30, height: 12 },
|
||||
itemNameRegion: { offsetX: 0, offsetY: -18, width: 220, height: 18 },
|
||||
@@ -56,17 +55,6 @@ test("detects OCR rectangles outside the captured frame", () => {
|
||||
assert.equal(isRectInside({ x: 90, y: 10, width: 20, height: 20 }, 100, 100), false);
|
||||
});
|
||||
|
||||
test("resolves the merchant region against the current window size", () => {
|
||||
assert.deepEqual(
|
||||
resolveScreenRect(
|
||||
{ xRatio: 0.25, yRatio: 0.1, widthRatio: 0.5, heightRatio: 0.08 },
|
||||
1200,
|
||||
800,
|
||||
),
|
||||
{ x: 300, y: 80, width: 600, height: 64 },
|
||||
);
|
||||
});
|
||||
|
||||
test("parses a hovered item tooltip", () => {
|
||||
assert.deepEqual(
|
||||
parseTooltipText("Tears of Eva\nPrice : 3,000,000 Adena\n(3 Million Adena)"),
|
||||
@@ -79,6 +67,16 @@ test("parses a hovered item tooltip", () => {
|
||||
assert.equal(parseTooltipText("Purchase List\nAdena 5,403,160"), null);
|
||||
});
|
||||
|
||||
test("takes the first OCR line as merchant name", () => {
|
||||
assert.equal(parseMerchantText(" Dwa \nVagabond\n"), "Dwa");
|
||||
test("parses trade side and merchant from the store header", () => {
|
||||
assert.deepEqual(parseStoreHeaderText("Private Store(Buy) - Hikvision"), {
|
||||
side: "buy",
|
||||
merchant: "Hikvision",
|
||||
rawText: "Private Store(Buy) - Hikvision",
|
||||
});
|
||||
assert.deepEqual(parseStoreHeaderText(" Private Store({Sell) - Dwa \n"), {
|
||||
side: "sell",
|
||||
merchant: "Dwa",
|
||||
rawText: "Private Store({Sell) - Dwa",
|
||||
});
|
||||
assert.equal(parseStoreHeaderText("Items on Sale"), null);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user