|
1 | 1 | import { beforeEach, describe, expect, it, mock } from "bun:test"; |
| 2 | +import { APIKeyType } from "@cossistant/types"; |
2 | 3 |
|
3 | 4 | const safelyExtractRequestDataMock = mock((async () => ({})) as ( |
4 | 5 | ...args: unknown[] |
@@ -101,6 +102,7 @@ mock.module("@api/realtime/emitter", () => ({ |
101 | 102 |
|
102 | 103 | mock.module("../middleware", () => ({ |
103 | 104 | protectedPublicApiKeyMiddleware: [], |
| 105 | + protectedPrivateApiKeyMiddleware: [], |
104 | 106 | })); |
105 | 107 |
|
106 | 108 | const contactRouterModulePromise = import("./contact"); |
@@ -226,9 +228,135 @@ describe("contact identify route", () => { |
226 | 228 | expect(identifyArg.email).toBeUndefined(); |
227 | 229 | }); |
228 | 230 |
|
| 231 | + it("returns 200 when visitorId is provided via X-Visitor-Id header", async () => { |
| 232 | + const db = {}; |
| 233 | + safelyExtractRequestDataMock.mockResolvedValue({ |
| 234 | + db, |
| 235 | + website: { |
| 236 | + id: "site-1", |
| 237 | + organizationId: "org-1", |
| 238 | + }, |
| 239 | + visitorIdHeader: "visitor-header-1", |
| 240 | + body: { |
| 241 | + externalId: "user-123", |
| 242 | + email: undefined, |
| 243 | + name: "User", |
| 244 | + image: undefined, |
| 245 | + metadata: undefined, |
| 246 | + contactOrganizationId: undefined, |
| 247 | + }, |
| 248 | + }); |
| 249 | + |
| 250 | + const { contactRouter } = await contactRouterModulePromise; |
| 251 | + const response = await contactRouter.request( |
| 252 | + new Request("http://localhost/identify", { |
| 253 | + method: "POST", |
| 254 | + }) |
| 255 | + ); |
| 256 | + const payload = (await response.json()) as { |
| 257 | + visitorId: string; |
| 258 | + }; |
| 259 | + |
| 260 | + expect(response.status).toBe(200); |
| 261 | + expect(findVisitorForWebsiteMock).toHaveBeenCalledWith(db, { |
| 262 | + visitorId: "visitor-header-1", |
| 263 | + websiteId: "site-1", |
| 264 | + }); |
| 265 | + expect(linkVisitorToContactMock).toHaveBeenCalledWith(db, { |
| 266 | + visitorId: "visitor-header-1", |
| 267 | + contactId: "contact-1", |
| 268 | + websiteId: "site-1", |
| 269 | + }); |
| 270 | + expect(payload.visitorId).toBe("visitor-header-1"); |
| 271 | + }); |
| 272 | + |
| 273 | + it("prefers body visitorId over X-Visitor-Id header when both are provided", async () => { |
| 274 | + const db = {}; |
| 275 | + safelyExtractRequestDataMock.mockResolvedValue({ |
| 276 | + db, |
| 277 | + website: { |
| 278 | + id: "site-1", |
| 279 | + organizationId: "org-1", |
| 280 | + }, |
| 281 | + visitorIdHeader: "visitor-header-1", |
| 282 | + body: { |
| 283 | + visitorId: "visitor-body-1", |
| 284 | + externalId: "user-123", |
| 285 | + email: undefined, |
| 286 | + name: "User", |
| 287 | + image: undefined, |
| 288 | + metadata: undefined, |
| 289 | + contactOrganizationId: undefined, |
| 290 | + }, |
| 291 | + }); |
| 292 | + |
| 293 | + const { contactRouter } = await contactRouterModulePromise; |
| 294 | + const response = await contactRouter.request( |
| 295 | + new Request("http://localhost/identify", { |
| 296 | + method: "POST", |
| 297 | + }) |
| 298 | + ); |
| 299 | + const payload = (await response.json()) as { |
| 300 | + visitorId: string; |
| 301 | + }; |
| 302 | + |
| 303 | + expect(response.status).toBe(200); |
| 304 | + expect(findVisitorForWebsiteMock).toHaveBeenCalledWith(db, { |
| 305 | + visitorId: "visitor-body-1", |
| 306 | + websiteId: "site-1", |
| 307 | + }); |
| 308 | + expect(linkVisitorToContactMock).toHaveBeenCalledWith(db, { |
| 309 | + visitorId: "visitor-body-1", |
| 310 | + contactId: "contact-1", |
| 311 | + websiteId: "site-1", |
| 312 | + }); |
| 313 | + expect(payload.visitorId).toBe("visitor-body-1"); |
| 314 | + }); |
| 315 | + |
| 316 | + it("returns 400 when neither body nor header provides a visitorId", async () => { |
| 317 | + safelyExtractRequestDataMock.mockResolvedValue({ |
| 318 | + db: {}, |
| 319 | + website: { |
| 320 | + id: "site-1", |
| 321 | + organizationId: "org-1", |
| 322 | + }, |
| 323 | + visitorIdHeader: null, |
| 324 | + body: { |
| 325 | + externalId: "user-123", |
| 326 | + email: undefined, |
| 327 | + name: "User", |
| 328 | + image: undefined, |
| 329 | + metadata: undefined, |
| 330 | + contactOrganizationId: undefined, |
| 331 | + }, |
| 332 | + }); |
| 333 | + |
| 334 | + const { contactRouter } = await contactRouterModulePromise; |
| 335 | + const response = await contactRouter.request( |
| 336 | + new Request("http://localhost/identify", { |
| 337 | + method: "POST", |
| 338 | + }) |
| 339 | + ); |
| 340 | + |
| 341 | + const payload = (await response.json()) as { |
| 342 | + error: string; |
| 343 | + message: string; |
| 344 | + }; |
| 345 | + |
| 346 | + expect(response.status).toBe(400); |
| 347 | + expect(payload).toEqual({ |
| 348 | + error: "BAD_REQUEST", |
| 349 | + message: "Visitor not found, please pass a valid visitorId", |
| 350 | + }); |
| 351 | + expect(findVisitorForWebsiteMock).toHaveBeenCalledTimes(0); |
| 352 | + expect(identifyContactMock).toHaveBeenCalledTimes(0); |
| 353 | + }); |
| 354 | + |
229 | 355 | it("POST /contacts returns 201 when externalId upsert creates a new contact", async () => { |
230 | 356 | safelyExtractRequestDataMock.mockResolvedValue({ |
231 | 357 | db: {}, |
| 358 | + apiKey: { keyType: APIKeyType.PRIVATE }, |
| 359 | + organization: { id: "org-1" }, |
232 | 360 | website: { |
233 | 361 | id: "site-1", |
234 | 362 | organizationId: "org-1", |
@@ -272,6 +400,8 @@ describe("contact identify route", () => { |
272 | 400 | it("POST /contacts returns 200 when externalId upsert updates an existing contact", async () => { |
273 | 401 | safelyExtractRequestDataMock.mockResolvedValue({ |
274 | 402 | db: {}, |
| 403 | + apiKey: { keyType: APIKeyType.PRIVATE }, |
| 404 | + organization: { id: "org-1" }, |
275 | 405 | website: { |
276 | 406 | id: "site-1", |
277 | 407 | organizationId: "org-1", |
@@ -307,6 +437,8 @@ describe("contact identify route", () => { |
307 | 437 | it("POST /contacts keeps create-only behavior when externalId is not provided", async () => { |
308 | 438 | safelyExtractRequestDataMock.mockResolvedValue({ |
309 | 439 | db: {}, |
| 440 | + apiKey: { keyType: APIKeyType.PRIVATE }, |
| 441 | + organization: { id: "org-1" }, |
310 | 442 | website: { |
311 | 443 | id: "site-1", |
312 | 444 | organizationId: "org-1", |
|
0 commit comments