This commit is contained in:
Dhruwang
2025-12-26 10:23:51 +05:30
parent f3704cecce
commit b252bf9fb7
2 changed files with 241 additions and 11 deletions

View File

@@ -20,7 +20,7 @@ vi.mock("@/modules/ee/contacts/lib/contact-attributes", () => ({
vi.mock("@formbricks/database", () => ({
prisma: {
$transaction: vi.fn(),
contactAttribute: { upsert: vi.fn() },
contactAttribute: { upsert: vi.fn(), findMany: vi.fn(), deleteMany: vi.fn() },
contactAttributeKey: { create: vi.fn() },
},
}));
@@ -52,17 +52,37 @@ const attributeKeys: TContactAttributeKey[] = [
type: "default",
environmentId,
},
{
id: "key-3",
key: "customAttr",
createdAt: new Date(),
updatedAt: new Date(),
isUnique: false,
name: "Custom Attribute",
description: null,
type: "custom",
environmentId,
},
];
describe("updateAttributes", () => {
beforeEach(() => {
vi.clearAllMocks();
// Set default mock return values - these will be overridden in individual tests
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([]);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
});
test("updates existing attributes", async () => {
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeys);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([
{ attributeKey: { key: "name" } },
{ attributeKey: { key: "email" } },
] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = { name: "John", email: "john@example.com" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
expect(prisma.$transaction).toHaveBeenCalled();
@@ -73,7 +93,12 @@ describe("updateAttributes", () => {
test("skips updating email if it already exists", async () => {
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeys);
vi.mocked(hasEmailAttribute).mockResolvedValue(true);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([
{ attributeKey: { key: "name" } },
{ attributeKey: { key: "email" } },
] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = { name: "John", email: "john@example.com" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
expect(prisma.$transaction).toHaveBeenCalled();
@@ -85,7 +110,9 @@ describe("updateAttributes", () => {
test("creates new attributes if under limit", async () => {
vi.mocked(getContactAttributeKeys).mockResolvedValue([attributeKeys[0]]);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([{ attributeKey: { key: "name" } }] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = { name: "John", newAttr: "val" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
expect(prisma.$transaction).toHaveBeenCalled();
@@ -97,7 +124,12 @@ describe("updateAttributes", () => {
test("does not create new attributes if over the limit", async () => {
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeys);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([
{ attributeKey: { key: "name" } },
{ attributeKey: { key: "email" } },
] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = { name: "John", newAttr: "val" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
expect(result.success).toBe(true);
@@ -107,10 +139,109 @@ describe("updateAttributes", () => {
test("returns success with no attributes to update or create", async () => {
vi.mocked(getContactAttributeKeys).mockResolvedValue([]);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([]);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = {};
const result = await updateAttributes(contactId, userId, environmentId, attributes);
expect(result.success).toBe(true);
expect(result.messages).toEqual([]);
});
test("deletes non-default attributes that are removed from payload", async () => {
// Reset mocks explicitly for this test
vi.mocked(prisma.contactAttribute.deleteMany).mockClear();
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeys);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([
{ attributeKey: { key: "name" } },
{ attributeKey: { key: "email" } },
{ attributeKey: { key: "customAttr" } },
] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 1 });
const attributes = { name: "John", email: "john@example.com" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
// Only customAttr (key-3) should be deleted, not name (key-1) or email (key-2)
expect(prisma.contactAttribute.deleteMany).toHaveBeenCalledWith({
where: {
contactId,
attributeKeyId: {
in: ["key-3"],
},
},
});
expect(result.success).toBe(true);
expect(result.messages).toEqual([]);
});
test("does not delete default attributes even if removed from payload", async () => {
// Reset mocks explicitly for this test
vi.mocked(prisma.contactAttribute.deleteMany).mockClear();
// Need to include userId and firstName in attributeKeys for this test
// Note: DEFAULT_ATTRIBUTES includes: email, userId, firstName, lastName (not "name")
const attributeKeysWithDefaults: TContactAttributeKey[] = [
{
id: "key-2",
key: "email",
createdAt: new Date(),
updatedAt: new Date(),
isUnique: false,
name: "Email",
description: null,
type: "default",
environmentId,
},
{
id: "key-4",
key: "userId",
createdAt: new Date(),
updatedAt: new Date(),
isUnique: false,
name: "User ID",
description: null,
type: "default",
environmentId,
},
{
id: "key-5",
key: "firstName",
createdAt: new Date(),
updatedAt: new Date(),
isUnique: false,
name: "First Name",
description: null,
type: "default",
environmentId,
},
{
id: "key-3",
key: "customAttr",
createdAt: new Date(),
updatedAt: new Date(),
isUnique: false,
name: "Custom Attribute",
description: null,
type: "custom",
environmentId,
},
];
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeysWithDefaults);
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
vi.mocked(prisma.contactAttribute.findMany).mockResolvedValue([
{ attributeKey: { key: "email" } },
{ attributeKey: { key: "userId" } },
{ attributeKey: { key: "firstName" } },
] as any);
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
vi.mocked(prisma.contactAttribute.deleteMany).mockResolvedValue({ count: 0 });
const attributes = { customAttr: "value" };
const result = await updateAttributes(contactId, userId, environmentId, attributes);
// Should not delete default attributes (email, userId, firstName) - deleteMany should not be called
// since all current attributes are default attributes
expect(prisma.contactAttribute.deleteMany).not.toHaveBeenCalled();
expect(result.success).toBe(true);
});
});

View File

@@ -21,6 +21,7 @@ describe("updateContactAttributes", () => {
const environmentId = "env123";
const userId = "user123";
const attributes = {
userId,
firstName: "John",
lastName: "Doe",
email: "john@example.com",
@@ -37,9 +38,39 @@ describe("updateContactAttributes", () => {
};
const mockCurrentKeys = [
{ id: "key1", key: "firstName", name: "First Name", environmentId },
{ id: "key2", key: "lastName", name: "Last Name", environmentId },
{ id: "key3", key: "email", name: "Email", environmentId },
{
id: "key1",
key: "firstName",
name: "First Name",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
{
id: "key2",
key: "lastName",
name: "Last Name",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
{
id: "key3",
key: "email",
name: "Email",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
];
const mockUpdatedAttributes = {
@@ -84,10 +115,42 @@ describe("updateContactAttributes", () => {
},
};
const mockCurrentKeys = [{ id: "key1", key: "firstName", name: "First Name", environmentId }];
const mockCurrentKeys = [
{
id: "key1",
key: "firstName",
name: "First Name",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
];
const mockUpdatedKeys = [
{ id: "key1", key: "firstName", name: "First Name", environmentId },
{ id: "key2", key: "newCustomField", name: "newCustomField", environmentId },
{
id: "key1",
key: "firstName",
name: "First Name",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
{
id: "key2",
key: "newCustomField",
name: "newCustomField",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "custom" as const,
isUnique: false,
description: null,
},
];
const mockUpdatedAttributes = {
@@ -107,7 +170,17 @@ describe("updateContactAttributes", () => {
expect(result.updatedAttributes).toEqual(mockUpdatedAttributes);
expect(result.updatedAttributeKeys).toEqual([
{ id: "key2", key: "newCustomField", name: "newCustomField", environmentId },
{
id: "key2",
key: "newCustomField",
name: "newCustomField",
environmentId,
createdAt: expect.any(Date),
updatedAt: expect.any(Date),
type: "custom",
isUnique: false,
description: null,
},
]);
});
@@ -126,7 +199,19 @@ describe("updateContactAttributes", () => {
},
};
const mockCurrentKeys = [{ id: "key1", key: "firstName", name: "First Name", environmentId }];
const mockCurrentKeys = [
{
id: "key1",
key: "firstName",
name: "First Name",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
];
const mockUpdatedAttributes = {
firstName: "John",
};
@@ -163,7 +248,19 @@ describe("updateContactAttributes", () => {
},
};
const mockCurrentKeys = [{ id: "key1", key: "email", name: "Email", environmentId }];
const mockCurrentKeys = [
{
id: "key1",
key: "email",
name: "Email",
environmentId,
createdAt: new Date(),
updatedAt: new Date(),
type: "default" as const,
isUnique: false,
description: null,
},
];
const mockUpdatedAttributes = {
email: "existing@example.com",
};
@@ -190,6 +287,8 @@ describe("updateContactAttributes", () => {
vi.mocked(getContact).mockResolvedValue(null);
await expect(updateContactAttributes(contactId, attributes)).rejects.toThrow("Contact not found");
await expect(updateContactAttributes(contactId, attributes)).rejects.toThrow(
"contact with ID contact123 not found"
);
});
});