@dossierhq/integration-test
Version:
Integration test to ensure that different Dossier database adapters work as expected.
148 lines • 6.52 kB
JavaScript
/// <reference types="./ReadOnlyEntityRepository.d.ts" />
import { copyEntity, EntityStatus, ok, withAdvisoryLock, } from '@dossierhq/core';
import { v5 as uuidv5 } from 'uuid';
import { assertExhaustive, assertOkResult, assertSame } from '../Asserts.js';
import { assertIsReadOnly, } from '../SchemaTypes.js';
const UUID_NAMESPACE = '10db07d4-3666-48e9-8080-12db0365ab81';
const ENTITIES_PER_CATEGORY = 5;
const ADVISORY_LOCK_NAME = 'integration-test-read-only-entities';
const READONLY_UPSERT = {
id: 'REPLACE',
info: { type: 'ReadOnly', name: `ReadOnly` },
fields: { message: 'Hello' },
};
export class ReadOnlyEntityRepository {
mainEntities;
secondaryEntities;
constructor(main, secondary) {
this.mainEntities = main;
this.secondaryEntities = secondary;
}
getMainPrincipalAdminEntities({ authKeys, status, } = {}) {
const checkAuthKeys = authKeys ?? [''];
const checkStatus = status ?? ['draft', 'published', 'modified', 'withdrawn'];
const entities = [
...this.mainEntities.filter((it) => checkAuthKeys.includes(it.info.authKey) && checkStatus.includes(it.info.status)),
...this.secondaryEntities.filter((it) => it.info.authKey === '' &&
checkAuthKeys.includes(it.info.authKey) &&
checkStatus.includes(it.info.status)),
];
return entities;
}
getMainPrincipalPublishedEntities(authKeys) {
const adminEntities = this.getMainPrincipalAdminEntities({ authKeys });
const publishedOnly = adminEntities.filter((it) => it.info.status === EntityStatus.published || it.info.status === EntityStatus.modified);
//TODO invalid since it always return the latest version
const publishedEntities = publishedOnly.map((it) => ({
id: it.id,
info: {
type: it.info.type,
name: it.info.name,
authKey: it.info.authKey,
valid: it.info.validPublished ?? false,
createdAt: it.info.createdAt,
},
fields: it.fields,
}));
return publishedEntities;
}
}
const createEntitiesPromises = {};
export async function createReadOnlyEntityRepository(clientProvider, databaseName) {
// Wrap in a promise to use the same result for all instances running in the same process
// If multiple databases (e.g. sqlite databases) are used in the same process, specify different
// names in the `databaseName` parameter
const resolvedName = databaseName ?? 'shared';
let promise = createEntitiesPromises[resolvedName];
if (!promise) {
promise = doCreateReadOnlyEntityRepository(clientProvider);
createEntitiesPromises[resolvedName] = promise;
}
return promise;
}
async function doCreateReadOnlyEntityRepository(clientProvider) {
const clientMain = clientProvider.dossierClient('main');
const clientSecondary = clientProvider.dossierClient('secondary');
return await withAdvisoryLock(clientMain, ADVISORY_LOCK_NAME, { acquireInterval: 500, leaseDuration: 2_000, renewInterval: 1_000 }, async (advisoryLock) => {
// Decide configurations for the entities
const entityConfigs = [];
for (const principal of ['secondary', 'main']) {
for (const authKey of ['', 'subject']) {
for (const status of Object.values(EntityStatus)) {
for (let index = 0; index < ENTITIES_PER_CATEGORY; index++) {
entityConfigs.push({ principal, authKey, status, index });
}
}
}
}
const mainEntities = [];
const secondaryEntities = [];
for (const { principal, authKey, status, index } of entityConfigs) {
if (!advisoryLock.active) {
return advisoryLock.renewError;
}
const client = principal === 'main' ? clientMain : clientSecondary;
const id = uuidv5(`${principal}-${authKey}-${status}-${index}`, UUID_NAMESPACE);
const result = await createEntity(client, id, authKey, status);
assertOkResult(result);
(principal === 'main' ? mainEntities : secondaryEntities).push(result.value);
}
return ok(new ReadOnlyEntityRepository(mainEntities, secondaryEntities));
});
}
async function createEntity(client, id, authKey, status) {
// Check if it already exists (to save some time)
const getResult = await client.getEntity({ id });
if (getResult.isOk() &&
getResult.value.info.authKey === authKey &&
getResult.value.info.status === status) {
assertIsReadOnly(getResult.value);
return ok(getResult.value);
}
// Create/upsert entity
const upsertResult = await client.upsertEntity(copyEntity(READONLY_UPSERT, { id, info: { authKey } }), {
publish: status === EntityStatus.withdrawn ||
status === EntityStatus.modified ||
status === EntityStatus.published,
});
if (upsertResult.isError())
return upsertResult;
let { entity } = upsertResult.value;
switch (status) {
case EntityStatus.draft:
break;
case EntityStatus.published:
break;
case EntityStatus.modified: {
const updateResult = await client.updateEntity({
id,
fields: { message: 'Updated message' },
});
if (updateResult.isError())
return updateResult;
entity = updateResult.value.entity;
break;
}
case EntityStatus.withdrawn: {
const unpublishResult = await client.unpublishEntities([{ id }]);
if (unpublishResult.isError())
return unpublishResult;
entity.info.status = unpublishResult.value[0].status;
entity.info.updatedAt = unpublishResult.value[0].updatedAt;
break;
}
case EntityStatus.archived: {
const archiveResult = await client.archiveEntity({ id });
if (archiveResult.isError())
return archiveResult;
entity.info.status = archiveResult.value.status;
entity.info.updatedAt = archiveResult.value.updatedAt;
break;
}
default:
assertExhaustive(status);
}
assertSame(entity.info.status, status);
return ok(entity);
}
//# sourceMappingURL=ReadOnlyEntityRepository.js.map