2023-06-29 18:26:45 +02:00
|
|
|
import o from "@tutao/otest"
|
2023-01-18 11:33:27 +01:00
|
|
|
import {
|
2025-06-03 12:08:18 +02:00
|
|
|
areValuesDifferent,
|
|
|
|
|
computePatches,
|
2024-08-23 13:00:37 +02:00
|
|
|
constructMailSetEntryId,
|
2023-01-18 11:33:27 +01:00
|
|
|
create,
|
2024-09-12 15:30:51 +02:00
|
|
|
deconstructMailSetEntryId,
|
2025-06-03 12:08:18 +02:00
|
|
|
GENERATED_MAX_ID,
|
2023-01-18 11:33:27 +01:00
|
|
|
GENERATED_MIN_ID,
|
|
|
|
|
generatedIdToTimestamp,
|
2023-03-29 17:48:38 +02:00
|
|
|
removeTechnicalFields,
|
2023-01-18 11:33:27 +01:00
|
|
|
timestampToGeneratedId,
|
|
|
|
|
timestampToHexGeneratedId,
|
2024-07-01 17:56:41 +02:00
|
|
|
} from "../../../../../src/common/api/common/utils/EntityUtils.js"
|
|
|
|
|
import { MailTypeRef } from "../../../../../src/common/api/entities/tutanota/TypeRefs.js"
|
|
|
|
|
import { typeModels } from "../../../../../src/common/api/entities/tutanota/TypeModels.js"
|
2024-01-09 15:47:35 +01:00
|
|
|
|
2025-06-03 12:08:18 +02:00
|
|
|
import { ClientModelEncryptedParsedInstance, ClientTypeModel, ElementEntity } from "../../../../../src/common/api/common/EntityTypes.js"
|
|
|
|
|
import { assertNotNull, base64ToUint8Array, clone, TypeRef, uint8ArrayToBase64 } from "@tutao/tutanota-utils"
|
2024-07-01 17:56:41 +02:00
|
|
|
import { hasError } from "../../../../../src/common/api/common/utils/ErrorUtils.js"
|
2025-06-03 12:08:18 +02:00
|
|
|
import {
|
|
|
|
|
dummyResolver,
|
|
|
|
|
TestAggregate,
|
|
|
|
|
testAggregateModel,
|
|
|
|
|
TestAggregateOnAggregate,
|
|
|
|
|
TestAggregateOnAggregateRef,
|
|
|
|
|
TestAggregateRef,
|
|
|
|
|
TestEntity,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
} from "../../worker/crypto/InstancePipelineTestUtils"
|
|
|
|
|
import { ClientTypeReferenceResolver, PatchOperationType, ServerTypeReferenceResolver } from "../../../../../src/common/api/common/EntityFunctions"
|
|
|
|
|
import { createTestEntityWithDummyResolver } from "../../../TestUtils"
|
|
|
|
|
import { InstancePipeline } from "../../../../../src/common/api/worker/crypto/InstancePipeline"
|
|
|
|
|
import { aes256RandomKey } from "@tutao/tutanota-crypto"
|
|
|
|
|
import { AttributeModel } from "../../../../../src/common/api/common/AttributeModel.js"
|
|
|
|
|
import { createPatch } from "../../../../../src/common/api/entities/sys/TypeRefs"
|
|
|
|
|
import { ValueType } from "../../../../../src/common/api/common/EntityConstants.js"
|
|
|
|
|
import { compressString } from "../../../../../src/common/api/worker/crypto/ModelMapper"
|
2023-01-18 11:33:27 +01:00
|
|
|
|
2022-01-07 15:58:30 +01:00
|
|
|
o.spec("EntityUtils", function () {
|
2022-12-27 15:37:40 +01:00
|
|
|
o("TimestampToHexGeneratedId ", function () {
|
|
|
|
|
let timestamp = 1370563200000
|
|
|
|
|
o(timestampToHexGeneratedId(timestamp, 0)).equals("4fc6fbb10000000000")
|
|
|
|
|
})
|
|
|
|
|
o("TimestampToHexGeneratedId server id 1", function () {
|
|
|
|
|
let timestamp = 1370563200000
|
|
|
|
|
o(timestampToHexGeneratedId(timestamp, 1)).equals("4fc6fbb10000000001")
|
|
|
|
|
})
|
|
|
|
|
o("generatedIdToTimestamp ", function () {
|
|
|
|
|
let maxTimestamp = Math.pow(2, 42) - 1
|
|
|
|
|
o(generatedIdToTimestamp(GENERATED_MIN_ID)).equals(0)
|
|
|
|
|
o(generatedIdToTimestamp(timestampToGeneratedId(0))).equals(0)
|
|
|
|
|
o(generatedIdToTimestamp("zzzzzzzzzzzz")).equals(maxTimestamp)
|
|
|
|
|
o(generatedIdToTimestamp("IwQvgF------")).equals(1370563200000)
|
|
|
|
|
})
|
2023-01-18 11:33:27 +01:00
|
|
|
|
2024-09-12 15:30:51 +02:00
|
|
|
o.spec("MailSetEntry id", function () {
|
|
|
|
|
o("constructMailSetEntryId", function () {
|
|
|
|
|
const mailId: Id = "-----------0"
|
2024-08-23 13:00:37 +02:00
|
|
|
|
2024-09-12 15:30:51 +02:00
|
|
|
const expected = "V7ifKQAAAAAAAAAAAQ"
|
2024-09-23 14:16:45 +02:00
|
|
|
const receiveDate = new Date("2017-10-03T13:46:13Z")
|
2024-08-23 13:00:37 +02:00
|
|
|
|
2024-09-12 15:30:51 +02:00
|
|
|
const calculatedId = constructMailSetEntryId(receiveDate, mailId)
|
|
|
|
|
o(expected).equals(calculatedId)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("deconstructMailSetEntryId", function () {
|
|
|
|
|
const setEntryId = "V7ifKQAAAAAAAAAAAQ"
|
|
|
|
|
const { receiveDate, mailId } = deconstructMailSetEntryId(setEntryId)
|
2024-09-23 14:16:45 +02:00
|
|
|
const diff = Math.abs(receiveDate.getTime() - new Date("2017-10-03T13:46:12.864Z").getTime())
|
|
|
|
|
o(diff < 10).equals(true)(`Expected a date near ${new Date("2017-10-03T13:46:12.864Z")}, got: ${receiveDate} with diff ${diff}`)
|
2024-09-12 15:30:51 +02:00
|
|
|
o(mailId).equals("-----------0")
|
|
|
|
|
})
|
2024-08-23 13:00:37 +02:00
|
|
|
})
|
|
|
|
|
|
2023-01-18 11:33:27 +01:00
|
|
|
o("create new entity without error object ", function () {
|
2025-03-10 16:19:11 +01:00
|
|
|
const mailEntity = create(typeModels[MailTypeRef.typeId], MailTypeRef)
|
2023-01-18 11:33:27 +01:00
|
|
|
o(mailEntity._errors).equals(undefined)
|
|
|
|
|
o(hasError(mailEntity)).equals(false)
|
|
|
|
|
|
|
|
|
|
o(mailEntity.subject).equals("") // value with default value
|
|
|
|
|
o(mailEntity.attachments).deepEquals([]) // association with Any cardinality
|
|
|
|
|
o(mailEntity.firstRecipient).equals(null) // association with ZeroOrOne cardinality
|
|
|
|
|
})
|
2023-03-29 17:48:38 +02:00
|
|
|
|
|
|
|
|
o.spec("removeTechnicalFields", function () {
|
2025-03-10 16:19:11 +01:00
|
|
|
const typeRef = { app: "sys", typeId: 9999999 } as TypeRef<unknown>
|
2023-03-29 17:48:38 +02:00
|
|
|
|
|
|
|
|
function makeEntity() {
|
|
|
|
|
return {
|
|
|
|
|
_id: "test",
|
|
|
|
|
// so that we can compare it
|
|
|
|
|
_type: typeRef,
|
|
|
|
|
_ownerGroup: null,
|
|
|
|
|
_ownerEncSessionKey: null,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o("it doesn't do anything when there's nothing to remove", function () {
|
|
|
|
|
const originalEntity = makeEntity()
|
|
|
|
|
const entityCopy = clone(originalEntity)
|
|
|
|
|
removeTechnicalFields(entityCopy as ElementEntity)
|
|
|
|
|
o(entityCopy as unknown).deepEquals(originalEntity)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("it removes _finalEncrypted fields directly on the entity", function () {
|
|
|
|
|
const originalEntity = { ...makeEntity(), _finalEncryptedThing: [1, 2, 3] }
|
|
|
|
|
const entityCopy = clone(originalEntity)
|
|
|
|
|
removeTechnicalFields(entityCopy as ElementEntity)
|
|
|
|
|
o(entityCopy as unknown).deepEquals({
|
|
|
|
|
_id: "test",
|
|
|
|
|
_type: typeRef,
|
|
|
|
|
_ownerGroup: null,
|
|
|
|
|
_ownerEncSessionKey: null,
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("it removes _finalEncrypted fields deeper in the entity", function () {
|
|
|
|
|
const originalEntity = {
|
|
|
|
|
...makeEntity(),
|
|
|
|
|
nested: {
|
|
|
|
|
test: "yes",
|
|
|
|
|
_finalEncryptedThing: [1, 2, 3],
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
const entityCopy = clone(originalEntity)
|
|
|
|
|
removeTechnicalFields(entityCopy as ElementEntity)
|
|
|
|
|
o(entityCopy as unknown).deepEquals({
|
|
|
|
|
_id: "test",
|
|
|
|
|
_type: typeRef,
|
|
|
|
|
_ownerGroup: null,
|
|
|
|
|
_ownerEncSessionKey: null,
|
|
|
|
|
nested: {
|
|
|
|
|
test: "yes",
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
2025-06-03 12:08:18 +02:00
|
|
|
|
|
|
|
|
o.spec("computePatches", function () {
|
|
|
|
|
const dummyTypeReferenceResolver = dummyResolver as ClientTypeReferenceResolver
|
|
|
|
|
const dummyInstancePipeline = new InstancePipeline(dummyResolver as ClientTypeReferenceResolver, dummyResolver as ServerTypeReferenceResolver)
|
|
|
|
|
|
|
|
|
|
o("computePatches returns empty list for equal objects", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on values in the root level", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
const date = new Date()
|
|
|
|
|
testEntity.testDate = date
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "5",
|
|
|
|
|
value: `${date.valueOf()}`,
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works when setting values to null", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testBoolean = null
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "7",
|
|
|
|
|
value: null,
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works when modifying multiple values", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
const date = new Date()
|
|
|
|
|
testEntity.testDate = date
|
|
|
|
|
testEntity.testBoolean = null
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "5",
|
|
|
|
|
value: `${date.valueOf()}`,
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "7",
|
|
|
|
|
value: null,
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on values on the aggregates", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testAssociation[0].testNumber = "1234"
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3/aggId/2",
|
|
|
|
|
value: "1234",
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on Any non-aggregation associations and additem operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testListElementAssociation.push(["listId", "elementId"], ["list2Id", "element2Id"])
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "8",
|
|
|
|
|
value: '[["listId","elementId"], ["list2Id", "element2Id"]]',
|
|
|
|
|
patchOperation: PatchOperationType.ADD_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on Any non-aggregation associations and removeitem operation", async function () {
|
|
|
|
|
const testEntity = await createTestEntityWithOriginal({
|
|
|
|
|
testListElementAssociation: [
|
|
|
|
|
["listId", "elementId"],
|
|
|
|
|
["listId2", "elementId2"],
|
|
|
|
|
["listId3", "elementId3"],
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
testEntity.testListElementAssociation.pop()
|
|
|
|
|
testEntity.testListElementAssociation.pop()
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
|
|
|
|
|
const objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "8",
|
|
|
|
|
value: '[["listId2","elementId2"], ["listId3", "elementId3"]]',
|
|
|
|
|
patchOperation: PatchOperationType.REMOVE_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on ZeroOrOne non-aggregation associations and replace operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testElementAssociation = "elementId"
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "4",
|
|
|
|
|
value: '["elementId"]',
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on ZeroOrOne non-aggregation list element associations and replace operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testZeroOrOneListElementAssociation = ["listId", "elementId"]
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "14",
|
|
|
|
|
value: '[["listId","elementId"]]',
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on ZeroOrOne non-aggregation associations and replace operation setting to null", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testElementAssociation = null
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "4",
|
|
|
|
|
value: "[]",
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on aggregations and additem operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testAssociation.push(await createTestEntityWithDummyResolver(TestAggregateRef, { _id: "newAgId" }))
|
|
|
|
|
testEntity.testAssociation.push(await createTestEntityWithDummyResolver(TestAggregateRef, { _id: "newAgId2" }))
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentEncryptedParsedInstance = await dummyInstancePipeline.cryptoMapper.encryptParsedInstance(
|
|
|
|
|
testTypeModel as ClientTypeModel,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
sk,
|
|
|
|
|
)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.typeMapper.applyDbTypes(testTypeModel as ClientTypeModel, currentEncryptedParsedInstance)
|
|
|
|
|
const testAssociationFirstEncryptedInstance = (
|
|
|
|
|
AttributeModel.getAttribute(currentEncryptedParsedInstance, "testAssociation", testTypeModel) as Array<ClientModelEncryptedParsedInstance>
|
|
|
|
|
)[1]
|
|
|
|
|
const testAssociationSecondEncryptedInstance = (
|
|
|
|
|
AttributeModel.getAttribute(currentEncryptedParsedInstance, "testAssociation", testTypeModel) as Array<ClientModelEncryptedParsedInstance>
|
|
|
|
|
)[2]
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3",
|
|
|
|
|
value: JSON.stringify([testAssociationFirstEncryptedInstance, testAssociationSecondEncryptedInstance]),
|
|
|
|
|
patchOperation: PatchOperationType.ADD_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on aggregations and removeitem operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
testEntity.testAssociation.pop()
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3",
|
|
|
|
|
value: '["aggId"]',
|
|
|
|
|
patchOperation: PatchOperationType.REMOVE_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on values on aggregations on aggregations and replace operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
const newValue = new Uint8Array(8)
|
|
|
|
|
testEntity.testAssociation[0].testSecondLevelAssociation[0].testBytes = newValue
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.mapAndEncrypt(TestTypeRef, testEntity, sk)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3/aggId/9/aggOnAggId/10",
|
|
|
|
|
value: uint8ArrayToBase64(newValue),
|
|
|
|
|
patchOperation: PatchOperationType.REPLACE,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on aggregates on aggregations and additem operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
|
|
|
|
|
const testAggregateOnAggregateEntity = await createTestEntityWithDummyResolver(TestAggregateOnAggregateRef)
|
|
|
|
|
testEntity.testAssociation[0].testSecondLevelAssociation.push(testAggregateOnAggregateEntity)
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentEncryptedParsedInstance = await dummyInstancePipeline.cryptoMapper.encryptParsedInstance(
|
|
|
|
|
testTypeModel as ClientTypeModel,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
sk,
|
|
|
|
|
)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.typeMapper.applyDbTypes(testTypeModel as ClientTypeModel, currentEncryptedParsedInstance)
|
|
|
|
|
const testAssociationEncrypted = AttributeModel.getAttribute(
|
|
|
|
|
currentEncryptedParsedInstance,
|
|
|
|
|
"testAssociation",
|
|
|
|
|
testTypeModel,
|
|
|
|
|
) as Array<ClientModelEncryptedParsedInstance>
|
|
|
|
|
const addedTestAggregateOnAggregateEncrypted = (
|
|
|
|
|
AttributeModel.getAttribute(
|
|
|
|
|
testAssociationEncrypted[0],
|
|
|
|
|
"testSecondLevelAssociation",
|
|
|
|
|
testAggregateModel,
|
|
|
|
|
) as Array<ClientModelEncryptedParsedInstance>
|
|
|
|
|
)[1]
|
|
|
|
|
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3/aggId/9",
|
|
|
|
|
value: JSON.stringify([addedTestAggregateOnAggregateEncrypted]),
|
|
|
|
|
patchOperation: PatchOperationType.ADD_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("computePatches works on aggregates on aggregations and removeitem operation", async function () {
|
|
|
|
|
const testEntity = await createFilledTestEntity()
|
|
|
|
|
|
|
|
|
|
testEntity.testAssociation[0].testSecondLevelAssociation.pop()
|
|
|
|
|
|
|
|
|
|
let sk = aes256RandomKey()
|
|
|
|
|
const originalParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(
|
|
|
|
|
TestTypeRef,
|
|
|
|
|
assertNotNull(testEntity._original),
|
|
|
|
|
)
|
|
|
|
|
const currentParsedInstance = await dummyInstancePipeline.modelMapper.mapToClientModelParsedInstance(TestTypeRef, testEntity)
|
|
|
|
|
const currentEncryptedParsedInstance = await dummyInstancePipeline.cryptoMapper.encryptParsedInstance(
|
|
|
|
|
testTypeModel as ClientTypeModel,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
sk,
|
|
|
|
|
)
|
|
|
|
|
const currentUntypedInstance = await dummyInstancePipeline.typeMapper.applyDbTypes(testTypeModel as ClientTypeModel, currentEncryptedParsedInstance)
|
|
|
|
|
let objectDiff = await computePatches(
|
|
|
|
|
originalParsedInstance,
|
|
|
|
|
currentParsedInstance,
|
|
|
|
|
currentUntypedInstance,
|
|
|
|
|
testTypeModel,
|
|
|
|
|
dummyTypeReferenceResolver,
|
|
|
|
|
)
|
|
|
|
|
o(objectDiff).deepEquals([
|
|
|
|
|
createPatch({
|
|
|
|
|
attributePath: "3/aggId/9",
|
|
|
|
|
value: '["aggOnAggId"]',
|
|
|
|
|
patchOperation: PatchOperationType.REMOVE_ITEM,
|
|
|
|
|
}),
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
async function createTestEntityWithOriginal(overrides: Partial<TestEntity>): Promise<TestEntity> {
|
|
|
|
|
const instance: TestEntity = await createTestEntityWithDummyResolver(TestTypeRef, overrides)
|
|
|
|
|
instance._original = structuredClone(instance)
|
|
|
|
|
return instance
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function createFilledTestEntity(): Promise<TestEntity> {
|
|
|
|
|
return await createTestEntityWithOriginal({
|
|
|
|
|
_type: TestTypeRef,
|
|
|
|
|
_finalIvs: {},
|
|
|
|
|
testAssociation: [
|
|
|
|
|
{
|
|
|
|
|
_type: TestAggregateRef,
|
|
|
|
|
_finalIvs: {},
|
|
|
|
|
_id: "aggId",
|
|
|
|
|
testNumber: "123456",
|
|
|
|
|
testSecondLevelAssociation: [
|
|
|
|
|
{
|
|
|
|
|
_type: TestAggregateOnAggregateRef,
|
|
|
|
|
_finalIvs: {},
|
|
|
|
|
_id: "aggOnAggId",
|
|
|
|
|
testBytes: null,
|
|
|
|
|
} as TestAggregateOnAggregate,
|
|
|
|
|
],
|
|
|
|
|
} as TestAggregate,
|
|
|
|
|
],
|
|
|
|
|
testBoolean: false,
|
|
|
|
|
testDate: new Date("2025-01-01T13:00:00.000Z"),
|
|
|
|
|
testElementAssociation: "associatedElementId",
|
|
|
|
|
testListElementAssociation: [["listId", "listElementId"]],
|
|
|
|
|
testZeroOrOneListElementAssociation: null,
|
|
|
|
|
testValue: "some encrypted string",
|
|
|
|
|
testGeneratedId: GENERATED_MIN_ID,
|
|
|
|
|
_id: [GENERATED_MIN_ID, GENERATED_MIN_ID],
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
o("areValuesDifferent works as expected", function () {
|
|
|
|
|
o(areValuesDifferent(ValueType.String, "example", "example")).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.String, "example", "different")).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.Number, 123, 123)).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.Number, 123, 456)).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.Bytes, base64ToUint8Array("byte"), base64ToUint8Array("byte"))).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.Bytes, base64ToUint8Array("byte"), base64ToUint8Array("diffbyte"))).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.Date, new Date(2025, 6, 6), new Date(2025, 6, 6))).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.Date, new Date(2025, 6, 6), new Date(2025, 6, 5))).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.Boolean, true, true)).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.Boolean, true, false)).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.GeneratedId, GENERATED_MIN_ID, GENERATED_MIN_ID)).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.GeneratedId, GENERATED_MIN_ID, GENERATED_MAX_ID)).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.GeneratedId, [GENERATED_MIN_ID, GENERATED_MIN_ID], [GENERATED_MIN_ID, GENERATED_MIN_ID])).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.GeneratedId, [GENERATED_MIN_ID, GENERATED_MIN_ID], [GENERATED_MAX_ID, GENERATED_MAX_ID])).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.CustomId, "customId", "customId")).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.CustomId, "customId", "diffcustomId")).equals(true)
|
|
|
|
|
o(areValuesDifferent(ValueType.CompressedString, "compress string", "compress string")).equals(false)
|
|
|
|
|
o(areValuesDifferent(ValueType.CompressedString, "compress string", "compress different string")).equals(true)
|
|
|
|
|
})
|
2022-12-27 15:37:40 +01:00
|
|
|
})
|