| |
| |
| |
| |
| |
| import { convexTest } from "convex-test"; |
| import { describe, expect, test } from "vitest"; |
| import schema from "../schema"; |
|
|
| const modules = import.meta.glob("../**/*.ts"); |
|
|
| describe("users schema invariants", () => { |
| test("required fields only — userId, firstSeenAt, lastSeenAt are sufficient to insert", async () => { |
| const t = convexTest(schema, modules); |
| const now = Date.now(); |
| await t.run(async (ctx) => { |
| await ctx.db.insert("users", { |
| userId: "u-required-only", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| }); |
| const row = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_userId", (q) => q.eq("userId", "u-required-only")) |
| .unique(), |
| ); |
| expect(row).not.toBeNull(); |
| expect(row?.email).toBeUndefined(); |
| expect(row?.localeTag).toBeUndefined(); |
| expect(row?.country).toBeUndefined(); |
| }); |
|
|
| test("all fields populated — round-trip preserves all values", async () => { |
| const t = convexTest(schema, modules); |
| const now = Date.now(); |
| await t.run(async (ctx) => { |
| await ctx.db.insert("users", { |
| userId: "u-full", |
| email: "alice@example.com", |
| normalizedEmail: "alice@example.com", |
| localeTag: "zh-Hant-TW", |
| localePrimary: "zh", |
| timezone: "Asia/Taipei", |
| country: "TW", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| }); |
| const row = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_userId", (q) => q.eq("userId", "u-full")) |
| .unique(), |
| ); |
| expect(row?.email).toBe("alice@example.com"); |
| expect(row?.localeTag).toBe("zh-Hant-TW"); |
| expect(row?.localePrimary).toBe("zh"); |
| expect(row?.timezone).toBe("Asia/Taipei"); |
| expect(row?.country).toBe("TW"); |
| }); |
|
|
| test("by_normalizedEmail index returns row by email", async () => { |
| const t = convexTest(schema, modules); |
| const now = Date.now(); |
| await t.run(async (ctx) => { |
| await ctx.db.insert("users", { |
| userId: "u-email-index", |
| normalizedEmail: "lookup@example.com", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| }); |
| const row = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_normalizedEmail", (q) => |
| q.eq("normalizedEmail", "lookup@example.com"), |
| ) |
| .unique(), |
| ); |
| expect(row?.userId).toBe("u-email-index"); |
| }); |
|
|
| test("by_localePrimary index returns matches", async () => { |
| const t = convexTest(schema, modules); |
| const now = Date.now(); |
| await t.run(async (ctx) => { |
| await ctx.db.insert("users", { |
| userId: "u-zh-1", |
| localePrimary: "zh", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| await ctx.db.insert("users", { |
| userId: "u-zh-2", |
| localePrimary: "zh", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| await ctx.db.insert("users", { |
| userId: "u-en-1", |
| localePrimary: "en", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| }); |
| const zhRows = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_localePrimary", (q) => q.eq("localePrimary", "zh")) |
| .collect(), |
| ); |
| expect(zhRows.length).toBe(2); |
| expect(zhRows.every((r) => r.localePrimary === "zh")).toBe(true); |
| }); |
|
|
| test("localeTag and localePrimary independently optional", async () => { |
| const t = convexTest(schema, modules); |
| const now = Date.now(); |
| |
| await t.run(async (ctx) => { |
| await ctx.db.insert("users", { |
| userId: "u-tag-only", |
| localeTag: "fr-CA", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| |
| await ctx.db.insert("users", { |
| userId: "u-primary-only", |
| localePrimary: "en", |
| firstSeenAt: now, |
| lastSeenAt: now, |
| }); |
| }); |
| const tagOnly = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_userId", (q) => q.eq("userId", "u-tag-only")) |
| .unique(), |
| ); |
| const primaryOnly = await t.run(async (ctx) => |
| await ctx.db |
| .query("users") |
| .withIndex("by_userId", (q) => q.eq("userId", "u-primary-only")) |
| .unique(), |
| ); |
| expect(tagOnly?.localeTag).toBe("fr-CA"); |
| expect(tagOnly?.localePrimary).toBeUndefined(); |
| expect(primaryOnly?.localeTag).toBeUndefined(); |
| expect(primaryOnly?.localePrimary).toBe("en"); |
| }); |
| }); |
|
|