test(25-01): add failing tests for upsertGlobalItem and bulkUpsertGlobalItems
- Import upsertGlobalItem and bulkUpsertGlobalItems (not yet exported) - Tests cover: create, conflict update, attribution fields, tag sync - Tests cover: empty tags clear, tags omitted leaves untouched - Tests cover: bulk upsert counts (created vs updated)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { beforeEach, describe, expect, it } from "bun:test";
|
import { beforeEach, describe, expect, it } from "bun:test";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
globalItems,
|
globalItems,
|
||||||
globalItemTags,
|
globalItemTags,
|
||||||
@@ -7,8 +8,10 @@ import {
|
|||||||
} from "../../src/db/schema.ts";
|
} from "../../src/db/schema.ts";
|
||||||
import { seedGlobalItems } from "../../src/db/seed-global-items.ts";
|
import { seedGlobalItems } from "../../src/db/seed-global-items.ts";
|
||||||
import {
|
import {
|
||||||
|
bulkUpsertGlobalItems,
|
||||||
getGlobalItemWithOwnerCount,
|
getGlobalItemWithOwnerCount,
|
||||||
searchGlobalItems,
|
searchGlobalItems,
|
||||||
|
upsertGlobalItem,
|
||||||
} from "../../src/server/services/global-item.service.ts";
|
} from "../../src/server/services/global-item.service.ts";
|
||||||
import { createTestDb } from "../helpers/db.ts";
|
import { createTestDb } from "../helpers/db.ts";
|
||||||
|
|
||||||
@@ -263,4 +266,153 @@ describe("Global Item Service", () => {
|
|||||||
expect(countAfterSecond).toBe(countAfterFirst);
|
expect(countAfterSecond).toBe(countAfterFirst);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("upsert operations", () => {
|
||||||
|
it("upsertGlobalItem creates new item and returns { item, created: true }", async () => {
|
||||||
|
const result = await upsertGlobalItem(db, {
|
||||||
|
brand: "Revelate Designs",
|
||||||
|
model: "Terrapin System",
|
||||||
|
category: "Bags",
|
||||||
|
weightGrams: 210,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.created).toBe(true);
|
||||||
|
expect(result.item.id).toBeDefined();
|
||||||
|
expect(result.item.brand).toBe("Revelate Designs");
|
||||||
|
expect(result.item.model).toBe("Terrapin System");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsertGlobalItem updates existing item on (brand, model) conflict and returns { item, created: false }", async () => {
|
||||||
|
await upsertGlobalItem(db, {
|
||||||
|
brand: "MSR",
|
||||||
|
model: "PocketRocket 2",
|
||||||
|
weightGrams: 83,
|
||||||
|
});
|
||||||
|
|
||||||
|
const second = await upsertGlobalItem(db, {
|
||||||
|
brand: "MSR",
|
||||||
|
model: "PocketRocket 2",
|
||||||
|
weightGrams: 90,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(second.created).toBe(false);
|
||||||
|
expect(second.item.weightGrams).toBe(90);
|
||||||
|
|
||||||
|
// Only one row should exist
|
||||||
|
const all = await db.select().from(globalItems);
|
||||||
|
expect(all).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsertGlobalItem persists sourceUrl, imageCredit, imageSourceUrl", async () => {
|
||||||
|
const result = await upsertGlobalItem(db, {
|
||||||
|
brand: "Apidura",
|
||||||
|
model: "Handlebar Pack",
|
||||||
|
sourceUrl: "https://apidura.com/shop/handlebar-pack/",
|
||||||
|
imageCredit: "Apidura Ltd",
|
||||||
|
imageSourceUrl: "https://apidura.com/images/handlebar-pack.jpg",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.item.sourceUrl).toBe("https://apidura.com/shop/handlebar-pack/");
|
||||||
|
expect(result.item.imageCredit).toBe("Apidura Ltd");
|
||||||
|
expect(result.item.imageSourceUrl).toBe("https://apidura.com/images/handlebar-pack.jpg");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsertGlobalItem with tags creates tags and links them", async () => {
|
||||||
|
const result = await upsertGlobalItem(db, {
|
||||||
|
brand: "Therm-a-Rest",
|
||||||
|
model: "NeoAir XLite",
|
||||||
|
tags: ["sleeping-pad", "ultralight"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.created).toBe(true);
|
||||||
|
|
||||||
|
const linkedTags = await db
|
||||||
|
.select({ name: tags.name })
|
||||||
|
.from(globalItemTags)
|
||||||
|
.innerJoin(tags, eq(globalItemTags.tagId, tags.id))
|
||||||
|
.where(eq(globalItemTags.globalItemId, result.item.id));
|
||||||
|
|
||||||
|
expect(linkedTags).toHaveLength(2);
|
||||||
|
const tagNames = linkedTags.map((t) => t.name).sort();
|
||||||
|
expect(tagNames).toEqual(["sleeping-pad", "ultralight"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsertGlobalItem without tags leaves existing tags untouched", async () => {
|
||||||
|
// Create item with tags
|
||||||
|
const first = await upsertGlobalItem(db, {
|
||||||
|
brand: "Sea to Summit",
|
||||||
|
model: "Spark III",
|
||||||
|
tags: ["sleeping-bag"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upsert without tags
|
||||||
|
await upsertGlobalItem(db, {
|
||||||
|
brand: "Sea to Summit",
|
||||||
|
model: "Spark III",
|
||||||
|
weightGrams: 450,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tags should remain
|
||||||
|
const linkedTags = await db
|
||||||
|
.select()
|
||||||
|
.from(globalItemTags)
|
||||||
|
.where(eq(globalItemTags.globalItemId, first.item.id));
|
||||||
|
|
||||||
|
expect(linkedTags).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("upsertGlobalItem with empty tags array clears existing tags", async () => {
|
||||||
|
// Create item with tags
|
||||||
|
const first = await upsertGlobalItem(db, {
|
||||||
|
brand: "Big Agnes",
|
||||||
|
model: "Copper Spur HV UL2",
|
||||||
|
tags: ["tent", "ultralight"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upsert with empty tags
|
||||||
|
await upsertGlobalItem(db, {
|
||||||
|
brand: "Big Agnes",
|
||||||
|
model: "Copper Spur HV UL2",
|
||||||
|
tags: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Tags should be cleared
|
||||||
|
const linkedTags = await db
|
||||||
|
.select()
|
||||||
|
.from(globalItemTags)
|
||||||
|
.where(eq(globalItemTags.globalItemId, first.item.id));
|
||||||
|
|
||||||
|
expect(linkedTags).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bulkUpsertGlobalItems processes array and returns correct created/updated counts", async () => {
|
||||||
|
const result = await bulkUpsertGlobalItems(db, [
|
||||||
|
{ brand: "Petzl", model: "Actik Core", weightGrams: 87 },
|
||||||
|
{ brand: "Black Diamond", model: "Spot 400", weightGrams: 95 },
|
||||||
|
{ brand: "Black Diamond", model: "Spot 350", weightGrams: 90 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.created).toBe(3);
|
||||||
|
expect(result.updated).toBe(0);
|
||||||
|
expect(result.items).toHaveLength(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("bulkUpsertGlobalItems handles mix of new and existing items", async () => {
|
||||||
|
// Pre-insert one item
|
||||||
|
await upsertGlobalItem(db, {
|
||||||
|
brand: "Petzl",
|
||||||
|
model: "Actik Core",
|
||||||
|
weightGrams: 87,
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await bulkUpsertGlobalItems(db, [
|
||||||
|
{ brand: "Petzl", model: "Actik Core", weightGrams: 90 }, // existing
|
||||||
|
{ brand: "Black Diamond", model: "Spot 400", weightGrams: 95 }, // new
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(result.created).toBe(1);
|
||||||
|
expect(result.updated).toBe(1);
|
||||||
|
expect(result.items).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user