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 { eq } from "drizzle-orm";
|
||||
import {
|
||||
globalItems,
|
||||
globalItemTags,
|
||||
@@ -7,8 +8,10 @@ import {
|
||||
} from "../../src/db/schema.ts";
|
||||
import { seedGlobalItems } from "../../src/db/seed-global-items.ts";
|
||||
import {
|
||||
bulkUpsertGlobalItems,
|
||||
getGlobalItemWithOwnerCount,
|
||||
searchGlobalItems,
|
||||
upsertGlobalItem,
|
||||
} from "../../src/server/services/global-item.service.ts";
|
||||
import { createTestDb } from "../helpers/db.ts";
|
||||
|
||||
@@ -263,4 +266,153 @@ describe("Global Item Service", () => {
|
||||
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