test(02-01): add failing tests for thread service

- Add threads and threadCandidates tables to Drizzle schema
- Add Zod schemas for thread/candidate/resolve validation
- Add Thread/ThreadCandidate types to shared types
- Update test helper with threads and thread_candidates tables
- Write comprehensive failing tests for thread service

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 11:36:01 +01:00
parent 2c4eb5b632
commit e146eeab80
5 changed files with 380 additions and 1 deletions

View File

@@ -28,6 +28,41 @@ export const items = sqliteTable("items", {
.$defaultFn(() => new Date()),
});
export const threads = sqliteTable("threads", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
status: text("status").notNull().default("active"),
resolvedCandidateId: integer("resolved_candidate_id"),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
});
export const threadCandidates = sqliteTable("thread_candidates", {
id: integer("id").primaryKey({ autoIncrement: true }),
threadId: integer("thread_id")
.notNull()
.references(() => threads.id, { onDelete: "cascade" }),
name: text("name").notNull(),
weightGrams: real("weight_grams"),
priceCents: integer("price_cents"),
categoryId: integer("category_id")
.notNull()
.references(() => categories.id),
notes: text("notes"),
productUrl: text("product_url"),
imageFilename: text("image_filename"),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" })
.notNull()
.$defaultFn(() => new Date()),
});
export const settings = sqliteTable("settings", {
key: text("key").primaryKey(),
value: text("value").notNull(),

View File

@@ -23,3 +23,28 @@ export const updateCategorySchema = z.object({
name: z.string().min(1).optional(),
emoji: z.string().min(1).max(4).optional(),
});
// Thread schemas
export const createThreadSchema = z.object({
name: z.string().min(1, "Thread name is required"),
});
export const updateThreadSchema = z.object({
name: z.string().min(1).optional(),
});
// Candidate schemas (same fields as items)
export const createCandidateSchema = z.object({
name: z.string().min(1, "Name is required"),
weightGrams: z.number().nonnegative().optional(),
priceCents: z.number().int().nonnegative().optional(),
categoryId: z.number().int().positive(),
notes: z.string().optional(),
productUrl: z.string().url().optional().or(z.literal("")),
});
export const updateCandidateSchema = createCandidateSchema.partial();
export const resolveThreadSchema = z.object({
candidateId: z.number().int().positive(),
});

View File

@@ -4,15 +4,27 @@ import type {
updateItemSchema,
createCategorySchema,
updateCategorySchema,
createThreadSchema,
updateThreadSchema,
createCandidateSchema,
updateCandidateSchema,
resolveThreadSchema,
} from "./schemas.ts";
import type { items, categories } from "../db/schema.ts";
import type { items, categories, threads, threadCandidates } from "../db/schema.ts";
// Types inferred from Zod schemas
export type CreateItem = z.infer<typeof createItemSchema>;
export type UpdateItem = z.infer<typeof updateItemSchema>;
export type CreateCategory = z.infer<typeof createCategorySchema>;
export type UpdateCategory = z.infer<typeof updateCategorySchema>;
export type CreateThread = z.infer<typeof createThreadSchema>;
export type UpdateThread = z.infer<typeof updateThreadSchema>;
export type CreateCandidate = z.infer<typeof createCandidateSchema>;
export type UpdateCandidate = z.infer<typeof updateCandidateSchema>;
export type ResolveThread = z.infer<typeof resolveThreadSchema>;
// Types inferred from Drizzle schema
export type Item = typeof items.$inferSelect;
export type Category = typeof categories.$inferSelect;
export type Thread = typeof threads.$inferSelect;
export type ThreadCandidate = typeof threadCandidates.$inferSelect;