mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-31 10:50:35 -06:00
Compare commits
1 Commits
v4.5.0-rc.
...
fix/user-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e165eaa45 |
@@ -144,7 +144,7 @@ describe("updateAttributes", () => {
|
||||
expect(result.messages).toEqual([]);
|
||||
});
|
||||
|
||||
test("deletes non-default attributes that are removed from payload", async () => {
|
||||
test("deletes non-default attributes when deleteRemovedAttributes is true", async () => {
|
||||
// Reset mocks explicitly for this test
|
||||
vi.mocked(prisma.contactAttribute.deleteMany).mockClear();
|
||||
|
||||
@@ -158,7 +158,8 @@ describe("updateAttributes", () => {
|
||||
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);
|
||||
// Pass deleteRemovedAttributes: true to enable deletion behavior
|
||||
const result = await updateAttributes(contactId, userId, environmentId, attributes, true);
|
||||
// Only customAttr (key-3) should be deleted, not name (key-1) or email (key-2)
|
||||
expect(prisma.contactAttribute.deleteMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
@@ -172,11 +173,31 @@ describe("updateAttributes", () => {
|
||||
expect(result.messages).toEqual([]);
|
||||
});
|
||||
|
||||
test("does not delete default attributes even if removed from payload", async () => {
|
||||
test("does not delete attributes when deleteRemovedAttributes is false (default behavior)", async () => {
|
||||
// Reset mocks explicitly for this test
|
||||
vi.mocked(prisma.contactAttribute.deleteMany).mockClear();
|
||||
|
||||
vi.mocked(getContactAttributeKeys).mockResolvedValue(attributeKeys);
|
||||
vi.mocked(getContactAttributes).mockResolvedValue({
|
||||
name: "Jane",
|
||||
email: "jane@example.com",
|
||||
customAttr: "oldValue",
|
||||
});
|
||||
vi.mocked(hasEmailAttribute).mockResolvedValue(false);
|
||||
vi.mocked(prisma.$transaction).mockResolvedValue(undefined);
|
||||
const attributes = { name: "John", email: "john@example.com" };
|
||||
// Default behavior (deleteRemovedAttributes: false) should NOT delete existing attributes
|
||||
const result = await updateAttributes(contactId, userId, environmentId, attributes);
|
||||
// deleteMany should NOT be called since we're merging, not replacing
|
||||
expect(prisma.contactAttribute.deleteMany).not.toHaveBeenCalled();
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.messages).toEqual([]);
|
||||
});
|
||||
|
||||
test("does not delete default attributes even when deleteRemovedAttributes is true", 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[] = [
|
||||
{
|
||||
@@ -234,7 +255,8 @@ describe("updateAttributes", () => {
|
||||
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);
|
||||
// Pass deleteRemovedAttributes: true to test that default attributes are still preserved
|
||||
const result = await updateAttributes(contactId, userId, environmentId, attributes, true);
|
||||
// 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();
|
||||
|
||||
@@ -47,11 +47,22 @@ const deleteAttributes = async (
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates or creates contact attributes.
|
||||
*
|
||||
* @param contactId - The ID of the contact to update
|
||||
* @param userId - The user ID of the contact
|
||||
* @param environmentId - The environment ID
|
||||
* @param contactAttributesParam - The attributes to update/create
|
||||
* @param deleteRemovedAttributes - When true, deletes attributes that exist in DB but are not in the payload.
|
||||
* Use this for UI forms where all attributes are submitted. Default is false (merge behavior) for API calls.
|
||||
*/
|
||||
export const updateAttributes = async (
|
||||
contactId: string,
|
||||
userId: string,
|
||||
environmentId: string,
|
||||
contactAttributesParam: TContactAttributes
|
||||
contactAttributesParam: TContactAttributes,
|
||||
deleteRemovedAttributes: boolean = false
|
||||
): Promise<{ success: boolean; messages?: string[]; ignoreEmailAttribute?: boolean }> => {
|
||||
validateInputs(
|
||||
[contactId, ZId],
|
||||
@@ -76,8 +87,12 @@ export const updateAttributes = async (
|
||||
const contactAttributes = existingEmailAttribute ? remainingAttributes : contactAttributesParam;
|
||||
const emailExists = !!existingEmailAttribute;
|
||||
|
||||
// Delete attributes that were removed (using the deleteAttributes service)
|
||||
await deleteAttributes(contactId, currentAttributes, contactAttributesParam, contactAttributeKeys);
|
||||
// Delete attributes that were removed (only when explicitly requested)
|
||||
// This is used by UI forms where all attributes are submitted
|
||||
// For API calls, we want merge behavior by default (only update passed attributes)
|
||||
if (deleteRemovedAttributes) {
|
||||
await deleteAttributes(contactId, currentAttributes, contactAttributesParam, contactAttributeKeys);
|
||||
}
|
||||
|
||||
// Create lookup map for attribute keys
|
||||
const contactAttributeKeyMap = new Map(contactAttributeKeys.map((ack) => [ack.key, ack]));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { updateAttributes } from "./attributes";
|
||||
import { getContactAttributeKeys } from "./contact-attribute-keys";
|
||||
import { getContactAttributes } from "./contact-attributes";
|
||||
@@ -16,7 +16,7 @@ describe("updateContactAttributes", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should update contact attributes successfully", async () => {
|
||||
test("should update contact attributes with deleteRemovedAttributes: true", async () => {
|
||||
const contactId = "contact123";
|
||||
const environmentId = "env123";
|
||||
const userId = "user123";
|
||||
@@ -91,13 +91,14 @@ describe("updateContactAttributes", () => {
|
||||
|
||||
expect(getContact).toHaveBeenCalledWith(contactId);
|
||||
expect(getContactAttributeKeys).toHaveBeenCalledWith(environmentId);
|
||||
expect(updateAttributes).toHaveBeenCalledWith(contactId, userId, environmentId, attributes);
|
||||
// Should call updateAttributes with deleteRemovedAttributes: true for UI form updates
|
||||
expect(updateAttributes).toHaveBeenCalledWith(contactId, userId, environmentId, attributes, true);
|
||||
expect(getContactAttributes).toHaveBeenCalledWith(contactId);
|
||||
expect(result.updatedAttributes).toEqual(mockUpdatedAttributes);
|
||||
expect(result.updatedAttributeKeys).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should detect new attribute keys when created", async () => {
|
||||
test("should detect new attribute keys when created", async () => {
|
||||
const contactId = "contact123";
|
||||
const environmentId = "env123";
|
||||
const userId = "user123";
|
||||
@@ -184,7 +185,7 @@ describe("updateContactAttributes", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("should handle missing userId with warning message", async () => {
|
||||
test("should handle missing userId with warning message", async () => {
|
||||
const contactId = "contact123";
|
||||
const environmentId = "env123";
|
||||
const attributes = {
|
||||
@@ -226,13 +227,13 @@ describe("updateContactAttributes", () => {
|
||||
|
||||
const result = await updateContactAttributes(contactId, attributes);
|
||||
|
||||
expect(updateAttributes).toHaveBeenCalledWith(contactId, "", environmentId, attributes);
|
||||
expect(updateAttributes).toHaveBeenCalledWith(contactId, "", environmentId, attributes, true);
|
||||
expect(result.messages).toContain(
|
||||
"Warning: userId attribute is missing. Some operations may not work correctly."
|
||||
);
|
||||
});
|
||||
|
||||
it("should merge messages from updateAttributes", async () => {
|
||||
test("should merge messages from updateAttributes", async () => {
|
||||
const contactId = "contact123";
|
||||
const environmentId = "env123";
|
||||
const userId = "user123";
|
||||
@@ -279,7 +280,7 @@ describe("updateContactAttributes", () => {
|
||||
expect(result.messages).toContain("The email already exists for this environment and was not updated.");
|
||||
});
|
||||
|
||||
it("should throw error if contact not found", async () => {
|
||||
test("should throw error if contact not found", async () => {
|
||||
const contactId = "contact123";
|
||||
const attributes = {
|
||||
firstName: "John",
|
||||
|
||||
@@ -13,11 +13,6 @@ export interface UpdateContactAttributesResult {
|
||||
updatedAttributeKeys?: TContactAttributeKey[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates contact attributes for a single contact.
|
||||
* Handles loading contact data, extracting userId, calling updateAttributes,
|
||||
* and detecting if new attribute keys were created.
|
||||
*/
|
||||
export const updateContactAttributes = async (
|
||||
contactId: string,
|
||||
attributes: TContactAttributes
|
||||
@@ -43,8 +38,9 @@ export const updateContactAttributes = async (
|
||||
const currentAttributeKeys = await getContactAttributeKeys(environmentId);
|
||||
const currentKeysSet = new Set(currentAttributeKeys.map((key) => key.key));
|
||||
|
||||
// Call the existing updateAttributes function
|
||||
const updateResult = await updateAttributes(contactId, userId, environmentId, attributes);
|
||||
// Call updateAttributes with deleteRemovedAttributes: true
|
||||
// UI forms submit all attributes, so any missing attribute should be deleted
|
||||
const updateResult = await updateAttributes(contactId, userId, environmentId, attributes, true);
|
||||
|
||||
// Merge any messages from updateAttributes
|
||||
if (updateResult.messages) {
|
||||
|
||||
Reference in New Issue
Block a user