|
5 | 5 | profiles, |
6 | 6 | profileTraitChanges, |
7 | 7 | } from "@databuddy/db/schema"; |
| 8 | +import { eq } from "@databuddy/db"; |
8 | 9 | import { appRouter, type Context } from "@databuddy/rpc"; |
| 10 | +import { splitTraits, upsertProfile } from "@databuddy/services/identity"; |
9 | 11 | import { |
10 | 12 | addToOrganization, |
11 | 13 | cleanup, |
@@ -179,3 +181,68 @@ describe("profiles.getHistory", () => { |
179 | 181 | ); |
180 | 182 | }); |
181 | 183 | }); |
| 184 | + |
| 185 | +describe("upsertProfile trait history", () => { |
| 186 | + iit("records baseline on first identify, diff on the next", async () => { |
| 187 | + const org = await insertOrganization(); |
| 188 | + const website = await insertWebsite({ organizationId: org.id }); |
| 189 | + |
| 190 | + await upsertProfile(website.id, "user_1", splitTraits({ plan: "free" })); |
| 191 | + await upsertProfile( |
| 192 | + website.id, |
| 193 | + "user_1", |
| 194 | + splitTraits({ plan: "pro" }), |
| 195 | + "billing" |
| 196 | + ); |
| 197 | + |
| 198 | + const rows = await db() |
| 199 | + .select() |
| 200 | + .from(profileTraitChanges) |
| 201 | + .where(eq(profileTraitChanges.profileId, "user_1")) |
| 202 | + .orderBy(profileTraitChanges.createdAt); |
| 203 | + |
| 204 | + expect(rows).toHaveLength(2); |
| 205 | + expect(rows[0]?.changes).toEqual({ plan: { old: null, new: "free" } }); |
| 206 | + expect(rows[0]?.source).toBe("identify"); |
| 207 | + expect(rows[1]?.changes).toEqual({ plan: { old: "free", new: "pro" } }); |
| 208 | + expect(rows[1]?.source).toBe("billing"); |
| 209 | + expect(rows[1]?.traits).toEqual({ plan: "pro" }); |
| 210 | + }); |
| 211 | + |
| 212 | + iit("writes no history row when traits are unchanged", async () => { |
| 213 | + const org = await insertOrganization(); |
| 214 | + const website = await insertWebsite({ organizationId: org.id }); |
| 215 | + |
| 216 | + await upsertProfile(website.id, "user_1", splitTraits({ plan: "pro" })); |
| 217 | + await upsertProfile(website.id, "user_1", splitTraits({ plan: "pro" })); |
| 218 | + |
| 219 | + const rows = await db() |
| 220 | + .select() |
| 221 | + .from(profileTraitChanges) |
| 222 | + .where(eq(profileTraitChanges.profileId, "user_1")); |
| 223 | + |
| 224 | + expect(rows).toHaveLength(1); |
| 225 | + }); |
| 226 | + |
| 227 | + iit("cascades history deletion when the profile is deleted", async () => { |
| 228 | + const org = await insertOrganization(); |
| 229 | + const website = await insertWebsite({ organizationId: org.id }); |
| 230 | + |
| 231 | + await upsertProfile(website.id, "user_1", splitTraits({ plan: "pro" })); |
| 232 | + expect( |
| 233 | + await db() |
| 234 | + .select() |
| 235 | + .from(profileTraitChanges) |
| 236 | + .where(eq(profileTraitChanges.profileId, "user_1")) |
| 237 | + ).toHaveLength(1); |
| 238 | + |
| 239 | + await db().delete(profiles).where(eq(profiles.profileId, "user_1")); |
| 240 | + |
| 241 | + expect( |
| 242 | + await db() |
| 243 | + .select() |
| 244 | + .from(profileTraitChanges) |
| 245 | + .where(eq(profileTraitChanges.profileId, "user_1")) |
| 246 | + ).toHaveLength(0); |
| 247 | + }); |
| 248 | +}); |
0 commit comments