- Add CachedOrder holdReasonCode/holdNote and ON_HOLD fulfillment status - Supplier UI: modal for hold reasons; vendor dashboard shows hold details - Sync to Shopify: native fulfillmentOrderHold (GraphQL) plus JUNO_ON_HOLD tag and juno.supplier_hold metafield; release holds when clearing - Preserve pending supplier fulfillment on webhook cache updates - Docker volume for gmail-reply-logos; settings note for API scopes Made-with: Cursor
237 lines
7.4 KiB
TypeScript
237 lines
7.4 KiB
TypeScript
import { describe, it, expect } from "vitest"
|
|
import {
|
|
newsletterSubscribeSchema,
|
|
storefrontChatPostSchema,
|
|
chatMessagePostSchema,
|
|
CHAT_MESSAGE_CONTENT_MAX,
|
|
CHAT_ATTACHMENT_JSON_MAX,
|
|
} from "./public"
|
|
import {
|
|
supplierProductCreateSchema,
|
|
supplierProductUpdateSchema,
|
|
productSyncRequestSchema,
|
|
supplierProfileUpsertSchema,
|
|
productSyncDecisionSchema,
|
|
} from "./business"
|
|
import { notificationsMarkReadSchema } from "./vendorMutations"
|
|
import { webPushSubscribeSchema, webPushUnsubscribeSchema } from "./notificationsPush"
|
|
import {
|
|
authVerifyMfaSchema,
|
|
kbFaqImportArraySchema,
|
|
supplierOfferUpdateSchema,
|
|
supplierOrderPatchSchema,
|
|
vendorInvitePostSchema,
|
|
} from "./extraMutations"
|
|
|
|
describe("newsletterSubscribeSchema", () => {
|
|
it("accepts valid email", () => {
|
|
const r = newsletterSubscribeSchema.safeParse({ email: " a@b.co " })
|
|
expect(r.success).toBe(true)
|
|
if (r.success) expect(r.data.email).toContain("@")
|
|
})
|
|
it("rejects invalid email", () => {
|
|
expect(newsletterSubscribeSchema.safeParse({ email: "nope" }).success).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("storefrontChatPostSchema", () => {
|
|
it("rejects empty content after trim", () => {
|
|
expect(
|
|
storefrontChatPostSchema.safeParse({
|
|
shop: "x.myshopify.com",
|
|
visitorId: "v1",
|
|
content: " ",
|
|
}).success,
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("chatMessagePostSchema", () => {
|
|
it("rejects oversize content", () => {
|
|
expect(
|
|
chatMessagePostSchema.safeParse({
|
|
receiverId: "u",
|
|
storeId: "s",
|
|
content: "x".repeat(CHAT_MESSAGE_CONTENT_MAX + 1),
|
|
}).success,
|
|
).toBe(false)
|
|
})
|
|
it("requires attachment for PRODUCT", () => {
|
|
expect(
|
|
chatMessagePostSchema.safeParse({
|
|
receiverId: "u",
|
|
storeId: "s",
|
|
kind: "PRODUCT",
|
|
}).success,
|
|
).toBe(false)
|
|
})
|
|
it("rejects huge attachment JSON", () => {
|
|
const big = { x: "y".repeat(CHAT_ATTACHMENT_JSON_MAX) }
|
|
expect(
|
|
chatMessagePostSchema.safeParse({
|
|
receiverId: "u",
|
|
storeId: "s",
|
|
kind: "PRODUCT",
|
|
attachment: big,
|
|
}).success,
|
|
).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("supplierProductCreateSchema", () => {
|
|
it("parses string price", () => {
|
|
const r = supplierProductCreateSchema.safeParse({
|
|
title: "T",
|
|
price: "12.50",
|
|
})
|
|
expect(r.success).toBe(true)
|
|
if (r.success) expect(r.data.price).toBe(12.5)
|
|
})
|
|
})
|
|
|
|
describe("supplierProductUpdateSchema", () => {
|
|
it("allows empty patch object", () => {
|
|
expect(supplierProductUpdateSchema.safeParse({}).success).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("productSyncRequestSchema", () => {
|
|
it("coerces numeric shopify id to string", () => {
|
|
const r = productSyncRequestSchema.safeParse({
|
|
shopifyProductId: 12345,
|
|
shopifyProductTitle: "Hi",
|
|
supplierId: "sup",
|
|
})
|
|
expect(r.success).toBe(true)
|
|
if (r.success) expect(r.data.shopifyProductId).toBe("12345")
|
|
})
|
|
})
|
|
|
|
describe("supplierProfileUpsertSchema", () => {
|
|
it("requires company name", () => {
|
|
expect(supplierProfileUpsertSchema.safeParse({ companyName: "" }).success).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("productSyncDecisionSchema", () => {
|
|
it("only allows accept/reject", () => {
|
|
expect(productSyncDecisionSchema.safeParse({ action: "accept" }).success).toBe(true)
|
|
expect(productSyncDecisionSchema.safeParse({ action: "hold" }).success).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("notificationsMarkReadSchema", () => {
|
|
it("requires markAll or ids", () => {
|
|
expect(notificationsMarkReadSchema.safeParse({ markAllAsRead: true }).success).toBe(true)
|
|
expect(
|
|
notificationsMarkReadSchema.safeParse({ notificationIds: ["a"] }).success,
|
|
).toBe(true)
|
|
expect(notificationsMarkReadSchema.safeParse({}).success).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("webPushSubscribeSchema", () => {
|
|
it("validates endpoint URL", () => {
|
|
expect(
|
|
webPushSubscribeSchema.safeParse({
|
|
endpoint: "not-a-url",
|
|
keys: { p256dh: "x", auth: "y" },
|
|
}).success,
|
|
).toBe(false)
|
|
expect(
|
|
webPushSubscribeSchema.safeParse({
|
|
endpoint: "https://example.com/push/123",
|
|
keys: { p256dh: "x", auth: "y" },
|
|
}).success,
|
|
).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("webPushUnsubscribeSchema", () => {
|
|
it("requires endpoint URL", () => {
|
|
expect(webPushUnsubscribeSchema.safeParse({ endpoint: "https://x.test/e" }).success).toBe(
|
|
true,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("authVerifyMfaSchema", () => {
|
|
it("requires 6-digit numeric code", () => {
|
|
expect(
|
|
authVerifyMfaSchema.safeParse({ email: "a@b.co", code: "123456" }).success,
|
|
).toBe(true)
|
|
expect(authVerifyMfaSchema.safeParse({ email: "a@b.co", code: "12345" }).success).toBe(
|
|
false,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("kbFaqImportArraySchema", () => {
|
|
it("rejects empty array", () => {
|
|
expect(kbFaqImportArraySchema.safeParse([]).success).toBe(false)
|
|
})
|
|
it("accepts one valid item", () => {
|
|
expect(
|
|
kbFaqImportArraySchema.safeParse([{ question: "Q?", answer: "A." }]).success,
|
|
).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("supplierOfferUpdateSchema", () => {
|
|
it("requires at least one field", () => {
|
|
expect(supplierOfferUpdateSchema.safeParse({}).success).toBe(false)
|
|
expect(supplierOfferUpdateSchema.safeParse({ price: 1 }).success).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("supplierOrderPatchSchema", () => {
|
|
it("allows empty patch", () => {
|
|
expect(supplierOrderPatchSchema.safeParse({}).success).toBe(true)
|
|
})
|
|
|
|
it("requires holdReasonCode when status is On hold", () => {
|
|
expect(
|
|
supplierOrderPatchSchema.safeParse({ fulfillmentStatus: "ON_HOLD" }).success,
|
|
).toBe(false)
|
|
expect(
|
|
supplierOrderPatchSchema.safeParse({
|
|
fulfillmentStatus: "ON_HOLD",
|
|
holdReasonCode: "inventory_out_of_stock",
|
|
}).success,
|
|
).toBe(true)
|
|
})
|
|
|
|
it("requires holdNote when reason is other", () => {
|
|
expect(
|
|
supplierOrderPatchSchema.safeParse({
|
|
fulfillmentStatus: "ON_HOLD",
|
|
holdReasonCode: "other",
|
|
}).success,
|
|
).toBe(false)
|
|
expect(
|
|
supplierOrderPatchSchema.safeParse({
|
|
fulfillmentStatus: "ON_HOLD",
|
|
holdReasonCode: "other",
|
|
holdNote: "Custom explanation",
|
|
}).success,
|
|
).toBe(true)
|
|
})
|
|
|
|
it("does not require hold fields for shipped", () => {
|
|
expect(
|
|
supplierOrderPatchSchema.safeParse({
|
|
fulfillmentStatus: "SHIPPED",
|
|
trackingNumber: "1Z",
|
|
trackingCompany: "UPS",
|
|
}).success,
|
|
).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe("vendorInvitePostSchema", () => {
|
|
it("rejects unknown keys", () => {
|
|
expect(vendorInvitePostSchema.safeParse({ connectToAllStores: true }).success).toBe(true)
|
|
expect(vendorInvitePostSchema.safeParse({ extra: 1 }).success).toBe(false)
|
|
})
|
|
})
|