fix: update all tests and MCP catalog tool for manufacturerId schema migration
This commit is contained in:
@@ -22,10 +22,10 @@ function errorResult(message: string): ToolResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const catalogItemInputSchema = {
|
const catalogItemInputSchema = {
|
||||||
brand: z.string().describe("Brand or manufacturer name"),
|
manufacturerSlug: z.string().describe("Manufacturer slug (e.g. 'revelate-designs', 'apidura')"),
|
||||||
model: z
|
model: z
|
||||||
.string()
|
.string()
|
||||||
.describe("Model name — combined with brand forms the unique identifier"),
|
.describe("Model name — combined with manufacturerSlug forms the unique identifier"),
|
||||||
category: z
|
category: z
|
||||||
.string()
|
.string()
|
||||||
.optional()
|
.optional()
|
||||||
@@ -80,7 +80,7 @@ export const catalogToolDefinitions = [
|
|||||||
export function registerCatalogTools(db: Db) {
|
export function registerCatalogTools(db: Db) {
|
||||||
return {
|
return {
|
||||||
upsert_catalog_item: async (args: {
|
upsert_catalog_item: async (args: {
|
||||||
brand: string;
|
manufacturerSlug: string;
|
||||||
model: string;
|
model: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
weightGrams?: number;
|
weightGrams?: number;
|
||||||
@@ -105,7 +105,7 @@ export function registerCatalogTools(db: Db) {
|
|||||||
|
|
||||||
bulk_upsert_catalog: async (args: {
|
bulk_upsert_catalog: async (args: {
|
||||||
items: Array<{
|
items: Array<{
|
||||||
brand: string;
|
manufacturerSlug: string;
|
||||||
model: string;
|
model: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
weightGrams?: number;
|
weightGrams?: number;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { manufacturers } from "../../src/db/schema.ts";
|
||||||
import { getCollectionSummary } from "../../src/server/mcp/resources/collection.ts";
|
import { getCollectionSummary } from "../../src/server/mcp/resources/collection.ts";
|
||||||
import { registerCatalogTools } from "../../src/server/mcp/tools/catalog.ts";
|
import { registerCatalogTools } from "../../src/server/mcp/tools/catalog.ts";
|
||||||
import { registerCategoryTools } from "../../src/server/mcp/tools/categories.ts";
|
import { registerCategoryTools } from "../../src/server/mcp/tools/categories.ts";
|
||||||
@@ -7,6 +8,16 @@ import { registerSetupTools } from "../../src/server/mcp/tools/setups.ts";
|
|||||||
import { registerThreadTools } from "../../src/server/mcp/tools/threads.ts";
|
import { registerThreadTools } from "../../src/server/mcp/tools/threads.ts";
|
||||||
import { createSecondTestUser, createTestDb } from "../helpers/db.ts";
|
import { createSecondTestUser, createTestDb } from "../helpers/db.ts";
|
||||||
|
|
||||||
|
async function insertManufacturer(db: any, name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [row] = await db
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.onConflictDoUpdate({ target: manufacturers.slug, set: { name } })
|
||||||
|
.returning();
|
||||||
|
return row!;
|
||||||
|
}
|
||||||
|
|
||||||
function parseResult(result: {
|
function parseResult(result: {
|
||||||
content: Array<{ type: string; text: string }>;
|
content: Array<{ type: string; text: string }>;
|
||||||
}) {
|
}) {
|
||||||
@@ -256,15 +267,15 @@ describe("MCP Collection Summary Resource", () => {
|
|||||||
describe("MCP Catalog Tools", () => {
|
describe("MCP Catalog Tools", () => {
|
||||||
test("upsert_catalog_item creates a new global item with created=true", async () => {
|
test("upsert_catalog_item creates a new global item with created=true", async () => {
|
||||||
const { db } = await createTestDb();
|
const { db } = await createTestDb();
|
||||||
|
await insertManufacturer(db, "Revelate Designs");
|
||||||
const tools = registerCatalogTools(db);
|
const tools = registerCatalogTools(db);
|
||||||
const result = await tools.upsert_catalog_item({
|
const result = await tools.upsert_catalog_item({
|
||||||
brand: "Revelate Designs",
|
manufacturerSlug: "revelate-designs",
|
||||||
model: "Terrapin System",
|
model: "Terrapin System",
|
||||||
weightGrams: 235,
|
weightGrams: 235,
|
||||||
priceCents: 16500,
|
priceCents: 16500,
|
||||||
});
|
});
|
||||||
const data = parseResult(result);
|
const data = parseResult(result);
|
||||||
expect(data.brand).toBe("Revelate Designs");
|
|
||||||
expect(data.model).toBe("Terrapin System");
|
expect(data.model).toBe("Terrapin System");
|
||||||
expect(data.created).toBe(true);
|
expect(data.created).toBe(true);
|
||||||
expect(data.id).toBeDefined();
|
expect(data.id).toBeDefined();
|
||||||
@@ -272,17 +283,18 @@ describe("MCP Catalog Tools", () => {
|
|||||||
|
|
||||||
test("upsert_catalog_item updates existing item on brand+model match", async () => {
|
test("upsert_catalog_item updates existing item on brand+model match", async () => {
|
||||||
const { db } = await createTestDb();
|
const { db } = await createTestDb();
|
||||||
|
await insertManufacturer(db, "Apidura");
|
||||||
const tools = registerCatalogTools(db);
|
const tools = registerCatalogTools(db);
|
||||||
|
|
||||||
// Create initial item
|
// Create initial item
|
||||||
await tools.upsert_catalog_item({
|
await tools.upsert_catalog_item({
|
||||||
brand: "Apidura",
|
manufacturerSlug: "apidura",
|
||||||
model: "Handlebar Pack",
|
model: "Handlebar Pack",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update it
|
// Update it
|
||||||
const result = await tools.upsert_catalog_item({
|
const result = await tools.upsert_catalog_item({
|
||||||
brand: "Apidura",
|
manufacturerSlug: "apidura",
|
||||||
model: "Handlebar Pack",
|
model: "Handlebar Pack",
|
||||||
description: "Updated description",
|
description: "Updated description",
|
||||||
weightGrams: 120,
|
weightGrams: 120,
|
||||||
@@ -295,10 +307,11 @@ describe("MCP Catalog Tools", () => {
|
|||||||
|
|
||||||
test("upsert_catalog_item includes attribution fields in result (SEED-03)", async () => {
|
test("upsert_catalog_item includes attribution fields in result (SEED-03)", async () => {
|
||||||
const { db } = await createTestDb();
|
const { db } = await createTestDb();
|
||||||
|
await insertManufacturer(db, "MSR");
|
||||||
const tools = registerCatalogTools(db);
|
const tools = registerCatalogTools(db);
|
||||||
|
|
||||||
const result = await tools.upsert_catalog_item({
|
const result = await tools.upsert_catalog_item({
|
||||||
brand: "MSR",
|
manufacturerSlug: "msr",
|
||||||
model: "PocketRocket 2",
|
model: "PocketRocket 2",
|
||||||
sourceUrl: "https://www.cascadedesigns.com/msr/pocket-rocket-2",
|
sourceUrl: "https://www.cascadedesigns.com/msr/pocket-rocket-2",
|
||||||
imageCredit: "MSR Photography",
|
imageCredit: "MSR Photography",
|
||||||
@@ -317,13 +330,16 @@ describe("MCP Catalog Tools", () => {
|
|||||||
|
|
||||||
test("bulk_upsert_catalog processes array and returns created/updated counts", async () => {
|
test("bulk_upsert_catalog processes array and returns created/updated counts", async () => {
|
||||||
const { db } = await createTestDb();
|
const { db } = await createTestDb();
|
||||||
|
await insertManufacturer(db, "Revelate Designs");
|
||||||
|
await insertManufacturer(db, "Apidura");
|
||||||
|
await insertManufacturer(db, "MSR");
|
||||||
const tools = registerCatalogTools(db);
|
const tools = registerCatalogTools(db);
|
||||||
|
|
||||||
const result = await tools.bulk_upsert_catalog({
|
const result = await tools.bulk_upsert_catalog({
|
||||||
items: [
|
items: [
|
||||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
{ manufacturerSlug: "revelate-designs", model: "Terrapin System" },
|
||||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
{ manufacturerSlug: "apidura", model: "Handlebar Pack" },
|
||||||
{ brand: "MSR", model: "PocketRocket 2" },
|
{ manufacturerSlug: "msr", model: "PocketRocket 2" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const data = parseResult(result);
|
const data = parseResult(result);
|
||||||
@@ -335,18 +351,20 @@ describe("MCP Catalog Tools", () => {
|
|||||||
|
|
||||||
test("bulk_upsert_catalog returns totalProcessed matching input length", async () => {
|
test("bulk_upsert_catalog returns totalProcessed matching input length", async () => {
|
||||||
const { db } = await createTestDb();
|
const { db } = await createTestDb();
|
||||||
|
await insertManufacturer(db, "Revelate Designs");
|
||||||
|
await insertManufacturer(db, "Apidura");
|
||||||
const tools = registerCatalogTools(db);
|
const tools = registerCatalogTools(db);
|
||||||
|
|
||||||
// Pre-create one item
|
// Pre-create one item
|
||||||
await tools.upsert_catalog_item({
|
await tools.upsert_catalog_item({
|
||||||
brand: "Revelate Designs",
|
manufacturerSlug: "revelate-designs",
|
||||||
model: "Terrapin System",
|
model: "Terrapin System",
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await tools.bulk_upsert_catalog({
|
const result = await tools.bulk_upsert_catalog({
|
||||||
items: [
|
items: [
|
||||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
{ manufacturerSlug: "revelate-designs", model: "Terrapin System" },
|
||||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
{ manufacturerSlug: "apidura", model: "Handlebar Pack" },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
const data = parseResult(result);
|
const data = parseResult(result);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { beforeEach, describe, expect, it } from "bun:test";
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { globalItems, setups } from "../../src/db/schema.ts";
|
import { globalItems, manufacturers, setups } from "../../src/db/schema.ts";
|
||||||
import { discoveryRoutes } from "../../src/server/routes/discovery.ts";
|
import { discoveryRoutes } from "../../src/server/routes/discovery.ts";
|
||||||
import { createTestDb } from "../helpers/db.ts";
|
import { createTestDb } from "../helpers/db.ts";
|
||||||
|
|
||||||
@@ -20,17 +20,28 @@ async function createTestApp() {
|
|||||||
return { app, db, userId };
|
return { app, db, userId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [row] = await db
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.onConflictDoUpdate({ target: manufacturers.slug, set: { name } })
|
||||||
|
.returning();
|
||||||
|
return row!;
|
||||||
|
}
|
||||||
|
|
||||||
async function insertGlobalItem(
|
async function insertGlobalItem(
|
||||||
db: TestDb["db"],
|
db: TestDb["db"],
|
||||||
brand: string,
|
brand: string,
|
||||||
model: string,
|
model: string,
|
||||||
category?: string,
|
category?: string,
|
||||||
) {
|
) {
|
||||||
|
const m = await insertManufacturer(db, brand);
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.insert(globalItems)
|
.insert(globalItems)
|
||||||
.values({ brand, model, category: category ?? "bags" })
|
.values({ manufacturerId: m.id, model, category: category ?? "bags" })
|
||||||
.returning();
|
.returning();
|
||||||
return row;
|
return row!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertPublicSetup(
|
async function insertPublicSetup(
|
||||||
@@ -142,14 +153,16 @@ describe("Discovery Routes", () => {
|
|||||||
const olderTime = new Date("2024-01-01T00:00:00Z");
|
const olderTime = new Date("2024-01-01T00:00:00Z");
|
||||||
const newerTime = new Date("2024-06-01T00:00:00Z");
|
const newerTime = new Date("2024-06-01T00:00:00Z");
|
||||||
|
|
||||||
|
const mA = await insertManufacturer(db, "Brand A");
|
||||||
|
const mB = await insertManufacturer(db, "Brand B");
|
||||||
await db.insert(globalItems).values({
|
await db.insert(globalItems).values({
|
||||||
brand: "Brand A",
|
manufacturerId: mA.id,
|
||||||
model: "Model A",
|
model: "Model A",
|
||||||
category: "bags",
|
category: "bags",
|
||||||
createdAt: olderTime,
|
createdAt: olderTime,
|
||||||
});
|
});
|
||||||
await db.insert(globalItems).values({
|
await db.insert(globalItems).values({
|
||||||
brand: "Brand B",
|
manufacturerId: mB.id,
|
||||||
model: "Model B",
|
model: "Model B",
|
||||||
category: "bags",
|
category: "bags",
|
||||||
createdAt: newerTime,
|
createdAt: newerTime,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
globalItems,
|
globalItems,
|
||||||
globalItemTags,
|
globalItemTags,
|
||||||
items,
|
items,
|
||||||
|
manufacturers,
|
||||||
tags,
|
tags,
|
||||||
} from "../../src/db/schema.ts";
|
} from "../../src/db/schema.ts";
|
||||||
import { globalItemRoutes } from "../../src/server/routes/global-items.ts";
|
import { globalItemRoutes } from "../../src/server/routes/global-items.ts";
|
||||||
@@ -25,16 +26,27 @@ async function createTestApp() {
|
|||||||
return { app, db, userId };
|
return { app, db, userId };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [row] = await db
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.onConflictDoUpdate({ target: manufacturers.slug, set: { name } })
|
||||||
|
.returning();
|
||||||
|
return row!;
|
||||||
|
}
|
||||||
|
|
||||||
async function insertGlobalItem(
|
async function insertGlobalItem(
|
||||||
db: TestDb["db"],
|
db: TestDb["db"],
|
||||||
brand: string,
|
brand: string,
|
||||||
model: string,
|
model: string,
|
||||||
) {
|
) {
|
||||||
|
const m = await insertManufacturer(db, brand);
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.insert(globalItems)
|
.insert(globalItems)
|
||||||
.values({ brand, model, category: "bags" })
|
.values({ manufacturerId: m.id, model, category: "bags" })
|
||||||
.returning();
|
.returning();
|
||||||
return row;
|
return row!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertItem(
|
async function insertItem(
|
||||||
@@ -113,18 +125,18 @@ describe("Global Item Routes", () => {
|
|||||||
|
|
||||||
describe("POST /api/global-items", () => {
|
describe("POST /api/global-items", () => {
|
||||||
it("returns 200 with item and created=true on new item", async () => {
|
it("returns 200 with item and created=true on new item", async () => {
|
||||||
|
await insertManufacturer(db, "Revelate Designs");
|
||||||
const res = await app.request("/api/global-items", {
|
const res = await app.request("/api/global-items", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
brand: "Revelate Designs",
|
manufacturerSlug: "revelate-designs",
|
||||||
model: "Terrapin System",
|
model: "Terrapin System",
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
|
|
||||||
const body = await res.json();
|
const body = await res.json();
|
||||||
expect(body.item.brand).toBe("Revelate Designs");
|
|
||||||
expect(body.item.model).toBe("Terrapin System");
|
expect(body.item.model).toBe("Terrapin System");
|
||||||
expect(body.created).toBe(true);
|
expect(body.created).toBe(true);
|
||||||
});
|
});
|
||||||
@@ -136,7 +148,7 @@ describe("Global Item Routes", () => {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
brand: "Revelate Designs",
|
manufacturerSlug: "revelate-designs",
|
||||||
model: "Terrapin System",
|
model: "Terrapin System",
|
||||||
description: "Updated description",
|
description: "Updated description",
|
||||||
}),
|
}),
|
||||||
@@ -148,7 +160,7 @@ describe("Global Item Routes", () => {
|
|||||||
expect(body.item.description).toBe("Updated description");
|
expect(body.item.description).toBe("Updated description");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when brand is missing", async () => {
|
it("returns 400 when manufacturerSlug is missing", async () => {
|
||||||
const res = await app.request("/api/global-items", {
|
const res = await app.request("/api/global-items", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
@@ -161,7 +173,7 @@ describe("Global Item Routes", () => {
|
|||||||
const res = await app.request("/api/global-items", {
|
const res = await app.request("/api/global-items", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ brand: "Revelate Designs" }),
|
body: JSON.stringify({ manufacturerSlug: "revelate-designs" }),
|
||||||
});
|
});
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
});
|
});
|
||||||
@@ -169,13 +181,15 @@ describe("Global Item Routes", () => {
|
|||||||
|
|
||||||
describe("POST /api/global-items/bulk", () => {
|
describe("POST /api/global-items/bulk", () => {
|
||||||
it("returns 200 with created/updated counts", async () => {
|
it("returns 200 with created/updated counts", async () => {
|
||||||
|
await insertManufacturer(db, "Revelate Designs");
|
||||||
|
await insertManufacturer(db, "Apidura");
|
||||||
const res = await app.request("/api/global-items/bulk", {
|
const res = await app.request("/api/global-items/bulk", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
items: [
|
items: [
|
||||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
{ manufacturerSlug: "revelate-designs", model: "Terrapin System" },
|
||||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
{ manufacturerSlug: "apidura", model: "Handlebar Pack" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -189,14 +203,15 @@ describe("Global Item Routes", () => {
|
|||||||
|
|
||||||
it("returns correct counts for mix of new and existing items", async () => {
|
it("returns correct counts for mix of new and existing items", async () => {
|
||||||
await insertGlobalItem(db, "Revelate Designs", "Terrapin System");
|
await insertGlobalItem(db, "Revelate Designs", "Terrapin System");
|
||||||
|
await insertManufacturer(db, "Apidura");
|
||||||
|
|
||||||
const res = await app.request("/api/global-items/bulk", {
|
const res = await app.request("/api/global-items/bulk", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
items: [
|
items: [
|
||||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
{ manufacturerSlug: "revelate-designs", model: "Terrapin System" },
|
||||||
{ brand: "Apidura", model: "Handlebar Pack" },
|
{ manufacturerSlug: "apidura", model: "Handlebar Pack" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
@@ -218,7 +233,7 @@ describe("Global Item Routes", () => {
|
|||||||
|
|
||||||
it("returns 400 when items array exceeds 100", async () => {
|
it("returns 400 when items array exceeds 100", async () => {
|
||||||
const items = Array.from({ length: 101 }, (_, i) => ({
|
const items = Array.from({ length: 101 }, (_, i) => ({
|
||||||
brand: `Brand${i}`,
|
manufacturerSlug: `brand${i}`,
|
||||||
model: `Model${i}`,
|
model: `Model${i}`,
|
||||||
}));
|
}));
|
||||||
const res = await app.request("/api/global-items/bulk", {
|
const res = await app.request("/api/global-items/bulk", {
|
||||||
@@ -229,14 +244,14 @@ describe("Global Item Routes", () => {
|
|||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 for invalid item in array (missing brand)", async () => {
|
it("returns 400 for invalid item in array (missing manufacturerSlug)", async () => {
|
||||||
const res = await app.request("/api/global-items/bulk", {
|
const res = await app.request("/api/global-items/bulk", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
items: [
|
items: [
|
||||||
{ brand: "Revelate Designs", model: "Terrapin System" },
|
{ manufacturerSlug: "revelate-designs", model: "Terrapin System" },
|
||||||
{ model: "Invalid Item without brand" },
|
{ model: "Invalid Item without manufacturerSlug" },
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import {
|
import {
|
||||||
globalItems,
|
globalItems,
|
||||||
items,
|
items,
|
||||||
|
manufacturers,
|
||||||
setupItems,
|
setupItems,
|
||||||
setups,
|
setups,
|
||||||
users,
|
users,
|
||||||
@@ -16,19 +17,34 @@ import { createTestDb } from "../helpers/db.ts";
|
|||||||
|
|
||||||
type TestDb = Awaited<ReturnType<typeof createTestDb>>;
|
type TestDb = Awaited<ReturnType<typeof createTestDb>>;
|
||||||
|
|
||||||
|
async function insertManufacturer(db: TestDb["db"], name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [existing] = await db
|
||||||
|
.select()
|
||||||
|
.from(manufacturers)
|
||||||
|
.where(eq(manufacturers.slug, slug));
|
||||||
|
if (existing) return existing;
|
||||||
|
const [row] = await db
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.returning();
|
||||||
|
return row!;
|
||||||
|
}
|
||||||
|
|
||||||
async function insertGlobalItem(
|
async function insertGlobalItem(
|
||||||
db: TestDb["db"],
|
db: TestDb["db"],
|
||||||
data: { brand: string; model: string; category?: string },
|
data: { brand: string; model: string; category?: string },
|
||||||
) {
|
) {
|
||||||
|
const m = await insertManufacturer(db, data.brand);
|
||||||
const [row] = await db
|
const [row] = await db
|
||||||
.insert(globalItems)
|
.insert(globalItems)
|
||||||
.values({
|
.values({
|
||||||
brand: data.brand,
|
manufacturerId: m.id,
|
||||||
model: data.model,
|
model: data.model,
|
||||||
category: data.category ?? null,
|
category: data.category ?? null,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
return row;
|
return row!;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function insertItem(db: TestDb["db"], userId: number, categoryId = 1) {
|
async function insertItem(db: TestDb["db"], userId: number, categoryId = 1) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, it } from "bun:test";
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
import { globalItems } from "../../src/db/schema.ts";
|
import { globalItems, manufacturers } from "../../src/db/schema.ts";
|
||||||
import {
|
import {
|
||||||
createItem,
|
createItem,
|
||||||
deleteItem,
|
deleteItem,
|
||||||
@@ -170,6 +170,15 @@ describe("Item Service", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("reference items (globalItemId)", () => {
|
describe("reference items (globalItemId)", () => {
|
||||||
|
async function insertManufacturer(testDb: any, name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [row] = await testDb
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.returning();
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
async function insertGlobalItem(
|
async function insertGlobalItem(
|
||||||
testDb: any,
|
testDb: any,
|
||||||
data: {
|
data: {
|
||||||
@@ -180,7 +189,14 @@ describe("Item Service", () => {
|
|||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const [row] = await testDb.insert(globalItems).values(data).returning();
|
const m = await insertManufacturer(testDb, data.brand);
|
||||||
|
const [row] = await testDb.insert(globalItems).values({
|
||||||
|
manufacturerId: m.id,
|
||||||
|
model: data.model,
|
||||||
|
weightGrams: data.weightGrams ?? null,
|
||||||
|
priceCents: data.priceCents ?? null,
|
||||||
|
imageUrl: data.imageUrl ?? null,
|
||||||
|
}).returning();
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, it } from "bun:test";
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
import { globalItems } from "../../src/db/schema.ts";
|
import { globalItems, manufacturers } from "../../src/db/schema.ts";
|
||||||
import {
|
import {
|
||||||
createCandidate,
|
createCandidate,
|
||||||
createThread,
|
createThread,
|
||||||
@@ -618,6 +618,15 @@ describe("Thread Service", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("catalog-linked candidates (globalItemId)", () => {
|
describe("catalog-linked candidates (globalItemId)", () => {
|
||||||
|
async function insertManufacturer(testDb: any, name: string) {
|
||||||
|
const slug = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
||||||
|
const [row] = await testDb
|
||||||
|
.insert(manufacturers)
|
||||||
|
.values({ name, slug, website: `https://${slug}.com` })
|
||||||
|
.returning();
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
async function insertGlobalItem(
|
async function insertGlobalItem(
|
||||||
testDb: any,
|
testDb: any,
|
||||||
data: {
|
data: {
|
||||||
@@ -628,7 +637,14 @@ describe("Thread Service", () => {
|
|||||||
imageUrl?: string;
|
imageUrl?: string;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const [row] = await testDb.insert(globalItems).values(data).returning();
|
const m = await insertManufacturer(testDb, data.brand);
|
||||||
|
const [row] = await testDb.insert(globalItems).values({
|
||||||
|
manufacturerId: m.id,
|
||||||
|
model: data.model,
|
||||||
|
weightGrams: data.weightGrams ?? null,
|
||||||
|
priceCents: data.priceCents ?? null,
|
||||||
|
imageUrl: data.imageUrl ?? null,
|
||||||
|
}).returning();
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user