Build the Solaire inventory intelligence MVP
Some checks failed
CI / verify (push) Has been cancelled
Some checks failed
CI / verify (push) Has been cancelled
Add the tenant-ready dashboard, freshness and pricing analytics, mock integration adapters, persistence schema, and verification tooling needed for the initial pilot. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
22
tests/e2e/dashboard.spec.ts
Normal file
22
tests/e2e/dashboard.spec.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("shows inventory intelligence overview", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
await expect(page.getByRole("heading", { name: "Good morning, Nora" })).toBeVisible();
|
||||
await expect(page.getByText("Inventory value")).toBeVisible();
|
||||
await expect(page.getByText("Freshness watchlist")).toBeVisible();
|
||||
});
|
||||
|
||||
test("moves from inventory to a product", async ({ page }) => {
|
||||
await page.goto("/inventory");
|
||||
await expect(page.getByRole("heading", { name: "Inventory" })).toBeVisible();
|
||||
await page.getByRole("link", { name: "Organic Strawberries" }).first().click();
|
||||
await expect(page.getByRole("heading", { name: "Organic Strawberries" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("reviews a recommendation", async ({ page }) => {
|
||||
await page.goto("/recommendations");
|
||||
await expect(page.getByRole("heading", { name: "Recommendations" })).toBeVisible();
|
||||
await page.getByRole("button", { name: "Reviewed" }).first().click();
|
||||
await expect(page.getByText("reviewed").first()).toBeVisible();
|
||||
});
|
||||
25
tests/freshness.test.ts
Normal file
25
tests/freshness.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { calculateFreshness, freshnessStatus } from "@/domain/freshness";
|
||||
|
||||
describe("freshness scoring", () => {
|
||||
const asOf = new Date("2026-07-25T12:00:00Z");
|
||||
|
||||
it("returns a fresh score for recently received stock", () => {
|
||||
expect(calculateFreshness("2026-07-23T12:00:00Z", 10, asOf)).toMatchObject({
|
||||
ageDays: 2,
|
||||
remainingDays: 8,
|
||||
score: 80,
|
||||
status: "fresh",
|
||||
});
|
||||
});
|
||||
|
||||
it("marks the threshold states consistently", () => {
|
||||
expect(freshnessStatus(45)).toBe("watch");
|
||||
expect(freshnessStatus(20)).toBe("at_risk");
|
||||
expect(freshnessStatus(0)).toBe("expired");
|
||||
});
|
||||
|
||||
it("never emits a negative freshness score", () => {
|
||||
expect(calculateFreshness("2026-07-01T12:00:00Z", 7, asOf).score).toBe(0);
|
||||
});
|
||||
});
|
||||
27
tests/integrations.test.ts
Normal file
27
tests/integrations.test.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getDashboardData } from "@/domain/analytics";
|
||||
import { runDemoSync } from "@/integrations/sync";
|
||||
|
||||
describe("mock integration sync", () => {
|
||||
it("normalizes Odoo and Salesforce fixtures", async () => {
|
||||
const [odoo, salesforce] = await Promise.all([
|
||||
runDemoSync("odoo"),
|
||||
runDemoSync("salesforce"),
|
||||
]);
|
||||
expect(odoo.status).toBe("succeeded");
|
||||
expect(salesforce.status).toBe("succeeded");
|
||||
expect(odoo.recordsWritten).toBeGreaterThan(0);
|
||||
expect(salesforce.recordsWritten).toBeGreaterThan(odoo.recordsWritten);
|
||||
});
|
||||
|
||||
it("keeps all demo data inside the pilot tenant", () => {
|
||||
const data = getDashboardData();
|
||||
expect(data.inventory.every((item) => item.organizationId === data.organization.id)).toBe(true);
|
||||
});
|
||||
|
||||
it("is idempotent for repeated fixture reads", async () => {
|
||||
const first = await runDemoSync("odoo");
|
||||
const second = await runDemoSync("odoo");
|
||||
expect(second.recordsWritten).toBe(first.recordsWritten);
|
||||
});
|
||||
});
|
||||
28
tests/recommendations.test.ts
Normal file
28
tests/recommendations.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getDashboardData } from "@/domain/analytics";
|
||||
import { marginPercent } from "@/domain/recommendations";
|
||||
|
||||
describe("pricing recommendations", () => {
|
||||
const data = getDashboardData();
|
||||
|
||||
it("protects every product margin floor", () => {
|
||||
for (const recommendation of data.recommendations) {
|
||||
const product = data.inventory.find((item) => item.productId === recommendation.productId)!.product;
|
||||
expect(marginPercent(product, recommendation.recommendedLow))
|
||||
.toBeGreaterThanOrEqual(product.marginFloorPercent - 0.01);
|
||||
}
|
||||
});
|
||||
|
||||
it("provides explanations and bounded ranges", () => {
|
||||
for (const recommendation of data.recommendations) {
|
||||
expect(recommendation.reason.length).toBeGreaterThan(20);
|
||||
expect(recommendation.recommendedHigh).toBeGreaterThanOrEqual(recommendation.recommendedLow);
|
||||
expect(recommendation.recommendedHigh).toBeLessThanOrEqual(recommendation.currentPrice);
|
||||
}
|
||||
});
|
||||
|
||||
it("contains actionable aging-stock recommendations", () => {
|
||||
expect(data.recommendations.some((item) => item.type === "markdown")).toBe(true);
|
||||
expect(data.recommendations.some((item) => item.type === "promotion")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user