UNPKG

@grouparoo/core

Version:
622 lines (548 loc) 20.5 kB
import { helper } from "@grouparoo/spec-helper"; import { Source, GrouparooRecord, RecordProperty, Property, GrouparooModel, } from "../../../src"; import { InvalidReasons } from "../../../src/models/RecordProperty"; describe("models/recordProperty", () => { let model: GrouparooModel; helper.grouparooTestServer({ truncate: true, enableTestPlugin: true }); beforeAll(async () => { model = await helper.factories.model(); }); let source: Source; let record: GrouparooRecord; let firstNameProperty: Property; let emailProperty: Property; let urlProperty: Property; let phoneNumberProperty: Property; let userIdProperty: Property; let lastLoginProperty: Property; let ltvProperty: Property; let vipProperty: Property; beforeAll(async () => { source = await helper.factories.source(); await source.setOptions({ table: "users" }); await source.bootstrapUniqueProperty({ key: "userId", type: "integer", mappedColumn: "id", }); await source.setMapping({ id: "userId" }); await source.update({ state: "ready" }); record = new GrouparooRecord({ modelId: model.id }); await record.save(); userIdProperty = await Property.findOne({ where: { key: "userId" }, }); firstNameProperty = await Property.create({ sourceId: source.id, key: "firstName", type: "string", }); await firstNameProperty.setOptions({ column: "firstName" }); await firstNameProperty.update({ state: "ready" }); emailProperty = await Property.create({ sourceId: source.id, key: "email", type: "email", }); await emailProperty.setOptions({ column: "email" }); await emailProperty.update({ state: "ready" }); urlProperty = await Property.create({ sourceId: source.id, key: "url", type: "url", }); await urlProperty.setOptions({ column: "url" }); await urlProperty.update({ state: "ready" }); phoneNumberProperty = await Property.create({ sourceId: source.id, key: "phoneNumber", type: "phoneNumber", }); await phoneNumberProperty.setOptions({ column: "phoneNumber" }); await phoneNumberProperty.update({ state: "ready" }); lastLoginProperty = await Property.create({ sourceId: source.id, key: "lastLoginAt", type: "date", }); await lastLoginProperty.setOptions({ column: "lastLoginAt" }); await lastLoginProperty.update({ state: "ready" }); ltvProperty = await Property.create({ sourceId: source.id, key: "ltv", type: "float", }); await ltvProperty.setOptions({ column: "ltv" }); await ltvProperty.update({ state: "ready" }); vipProperty = await Property.create({ sourceId: source.id, key: "isVIP", type: "boolean", }); await vipProperty.setOptions({ column: "isVIP" }); await vipProperty.update({ state: "ready" }); }); describe("array properties", () => { let purchasesProperty: Property; beforeAll(async () => { purchasesProperty = await Property.create({ sourceId: source.id, key: "purchases", type: "string", isArray: true, }); await purchasesProperty.setOptions({ column: "purchases" }); await purchasesProperty.update({ state: "ready" }); }); afterAll(async () => { await purchasesProperty.destroy(); }); test("by default record properties have a position of 0", async () => { const property = await RecordProperty.create({ recordId: record.id, propertyId: purchasesProperty.id, rawValue: "hat", }); expect(property.position).toBe(0); await property.destroy(); }); test("multiple values can be set with different positions", async () => { const propertyA = await RecordProperty.create({ recordId: record.id, propertyId: purchasesProperty.id, rawValue: "hat", position: 1, }); const propertyB = await RecordProperty.create({ recordId: record.id, propertyId: purchasesProperty.id, rawValue: "shoe", position: 0, }); expect(propertyA.position).toBe(1); expect(propertyB.position).toBe(0); await propertyA.destroy(); await propertyB.destroy(); }); test("multiple values cannot re-use the same position", async () => { const property = await RecordProperty.create({ recordId: record.id, propertyId: purchasesProperty.id, rawValue: "hat", }); await expect( RecordProperty.create({ recordId: record.id, propertyId: purchasesProperty.id, rawValue: "hat", }) ).rejects.toThrow(/Validation error/); await property.destroy(); }); }); describe("type coercion", () => { test("strings", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: firstNameProperty.id, }); await recordProperty.setValue("Mario"); const response = await recordProperty.getValue(); expect(response).toBe("Mario"); }); describe("emails", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); await recordProperty.setValue("mario@example.com"); const response = await recordProperty.getValue(); expect(response).toBe("mario@example.com"); }); test("emails are not lower cased", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); await recordProperty.setValue("MARIO@example.com"); const response = await recordProperty.getValue(); expect(response).toBe("MARIO@example.com"); }); test("invalid emails set invalidValue", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); const badEmails = [ "someone", "someone.com", "someone.com@", "someone@site", "someone @site.com", ]; for (const e of badEmails) { await recordProperty.setValue(e); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe(e); expect(recordProperty.invalidReason).toBe("Invalid email value"); } }); test("weird emails validate", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); const weirdEmailsGood = [ "email@example.com", "firstname.lastname@example.com", "email@subdomain.example.com", "firstname+lastname@example.com", "email@123.123.123.123", "email@[123.123.123.123]", '"email"@example.com', "1234567890@example.com", "email@example-one.com", "_______@example.com", "email@example.name", "email@example.museum", "email@example.co.jp", "firstname-lastname@example.com", "very.unusual.”@”.unusual.com@example.com", ]; const weirdEmailsBad = [ "much.”more unusual”@example.com", 'very.”(),:;<>[]”.VERY.”very@\\ "very”.unusual@strange.example.com', ]; for (const e of weirdEmailsGood) { await recordProperty.setValue(e); const response = await recordProperty.getValue(); expect(response).toBe(e); expect(recordProperty.invalidValue).toBe(null); expect(recordProperty.invalidReason).toBe(null); } for (const e of weirdEmailsBad) { await recordProperty.setValue(e); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe(e); expect(recordProperty.invalidReason).toBe("Invalid email value"); } }); test("very long emails are valid", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); const value = "Deleted-user-id-19430-Team-5051deleted-user-id-19430-team-5051XXXXXX@example.com"; await recordProperty.setValue(value); const response = await recordProperty.getValue(); expect(response).toBe(value); }); }); describe("urls", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: urlProperty.id, }); await recordProperty.setValue("HTTPS://grouparoo.com/picture"); const response = await recordProperty.getValue(); expect(response).toBe("https://grouparoo.com/picture"); }); test("invalid urls set invalidValue", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: urlProperty.id, }); await recordProperty.setValue("not a url"); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe("not a url"); expect(recordProperty.invalidReason).toBe("Invalid url value"); }); }); describe("phone numbers", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: phoneNumberProperty.id, }); await recordProperty.setValue("4128889999"); const response = await recordProperty.getValue(); expect(response).toBe("+1 412 888 9999"); }); test("invalid phone numbers are nulled out", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: phoneNumberProperty.id, }); await recordProperty.setValue("5"); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe("5"); expect(recordProperty.invalidReason).toBe("Invalid phoneNumber value"); }); }); describe("integers", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: userIdProperty.id, }); await recordProperty.setValue(123); const response = await recordProperty.getValue(); expect(response).toBe(123); }); test("invalid", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: userIdProperty.id, }); await recordProperty.setValue("fish"); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe("fish"); expect(recordProperty.invalidReason).toBe("Invalid integer value"); }); }); describe("float", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: ltvProperty.id, }); await recordProperty.setValue(100.21); const response = await recordProperty.getValue(); expect(response).toBe(100.21); }); test("floats (invalid)", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: ltvProperty.id, }); await recordProperty.setValue("foo"); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe("foo"); expect(recordProperty.invalidReason).toBe("Invalid float value"); }); }); describe("dates", () => { test("dates (object form)", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: lastLoginProperty.id, }); await recordProperty.setValue(new Date(0)); const response = (await recordProperty.getValue()) as Date; expect(response.getFullYear()).toBeGreaterThanOrEqual(1969); expect(response.getFullYear()).toBeLessThanOrEqual(1970); }); test("dates (timestamp form)", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: lastLoginProperty.id, }); await recordProperty.setValue(0); const response = (await recordProperty.getValue()) as Date; expect(response.getFullYear()).toBeGreaterThanOrEqual(1969); expect(response.getFullYear()).toBeLessThanOrEqual(1970); }); test("dates (invalid)", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: lastLoginProperty.id, }); await recordProperty.setValue("mushroom"); const response = (await recordProperty.getValue()) as Date; expect(response).toEqual(null); expect(recordProperty.invalidValue).toBe("mushroom"); expect(recordProperty.invalidReason).toBe("Invalid date value"); }); }); describe("booleans", () => { test("work", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: vipProperty.id, }); await recordProperty.setValue(true); let response = await recordProperty.getValue(); expect(response).toBe(true); await recordProperty.setValue("true"); response = await recordProperty.getValue(); expect(response).toBe(true); await recordProperty.setValue(1); response = await recordProperty.getValue(); expect(response).toBe(true); await recordProperty.setValue(false); response = await recordProperty.getValue(); expect(response).toBe(false); await recordProperty.setValue("false"); response = await recordProperty.getValue(); expect(response).toBe(false); await recordProperty.setValue(0); response = await recordProperty.getValue(); expect(response).toBe(false); }); test("invalid", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: vipProperty.id, }); await recordProperty.setValue("hat"); const response = await recordProperty.getValue(); expect(response).toBe(null); expect(recordProperty.invalidValue).toBe("hat"); expect(recordProperty.invalidReason).toBe("Invalid boolean value"); }); }); describe("null", () => { test("string rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: firstNameProperty.id, }); await recordProperty.setValue(null); const response = await recordProperty.getValue(); expect(response).toBe(null); }); test("email rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: emailProperty.id, }); await recordProperty.setValue(null); const response = await recordProperty.getValue(); expect(response).toBe(null); }); test("integer rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: userIdProperty.id, }); await recordProperty.setValue(null); const response = await recordProperty.getValue(); expect(response).toBe(null); }); test("date rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: lastLoginProperty.id, }); await recordProperty.setValue(null); const response = await recordProperty.getValue(); expect(response).toBe(null); }); test("float rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: ltvProperty.id, }); await recordProperty.setValue(null); const response = await recordProperty.getValue(); expect(response).toBe(null); }); test("boolean rules can be null", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: vipProperty.id, }); await recordProperty.setValue(null); let response = await recordProperty.getValue(); expect(response).toBe(null); }); }); test("it will not save a key that is not defined as a property", async () => { const recordProperty = new RecordProperty({ recordId: record.id, propertyId: "abc", }); await expect(recordProperty.setValue(true)).rejects.toThrow( /property not found for propertyId/ ); }); }); describe("uniqueness and cascade destruction", () => { let secondRecord: GrouparooRecord; beforeAll(async () => { emailProperty.unique = true; await emailProperty.save(); await record.buildNullProperties(); secondRecord = new GrouparooRecord({ modelId: model.id }); await secondRecord.save(); }); afterAll(async () => { emailProperty.unique = false; await emailProperty.save(); }); test("allows the addition of another unique, non-conflicting property", async () => { await record.markPending(); await secondRecord.markPending(); await record.addOrUpdateProperties({ email: ["mario@example.com"] }); await secondRecord.addOrUpdateProperties({ email: ["luigi@example.com"], }); }); test("recordProperties that violate the uniqueness rule will noted", async () => { await record.markPending(); await secondRecord.markPending(); await record.addOrUpdateProperties({ email: ["mario@example.com"] }); await secondRecord.addOrUpdateProperties({ email: ["mario@example.com"], }); // does not throw const properties = await secondRecord.getProperties(); expect(properties.email.values).toEqual([null]); expect(properties.email.invalidValue).toEqual("mario@example.com"); expect(properties.email.invalidReason).toEqual(InvalidReasons.Duplicate); expect(properties.email.invalidReason).toEqual("Duplicate Value"); }); test("editing the key of a property renames all the record properties that have that key", async () => { await record.addOrUpdateProperties({ email: ["mario@example.com"] }); await secondRecord.addOrUpdateProperties({ email: ["luigi@example.com"], }); const beforeCount = await RecordProperty.count({ where: { propertyId: emailProperty.id }, }); expect(beforeCount).toBe(2); emailProperty.key = "EMAIL!"; await emailProperty.save(); const afterCount = await RecordProperty.count({ where: { propertyId: emailProperty.id }, }); expect(afterCount).toBe(2); const property = await RecordProperty.findOne({ where: { propertyId: emailProperty.id }, }); const apiData = await property.apiData(); expect(apiData.key).toBe("EMAIL!"); emailProperty.key = "email"; await emailProperty.save(); }); test("deleting a property deletes all the record properties that have that key", async () => { await record.addOrUpdateProperties({ email: ["mario@example.com"] }); await secondRecord.addOrUpdateProperties({ email: ["luigi@example.com"], }); const beforeCount = await RecordProperty.count({ where: { propertyId: emailProperty.id }, }); expect(beforeCount).toBe(2); await emailProperty.destroy(); const afterCount = await RecordProperty.count({ where: { propertyId: emailProperty.id }, }); expect(afterCount).toBe(0); }); }); });