All files / realm/blong-party/adapter/dbTest partyHierarchyMerge.ts

100% Statements 144/144
58.82% Branches 10/17
100% Functions 4/4
100% Lines 144/144

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 1451x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 10x 1x 1x 1x 16x 16x 16x 16x 1x 1x 2x 2x 2x 2x 1x 1x 1x 4x 4x 4x 4x 4x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 2x 2x 2x 2x 2x 2x 4x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 1x 1x 1x 1x 1x  
import {type IMeta, handler} from '@feasibleone/blong';
 
// The knex query builder is intentionally untyped here (matches the access realm's db handlers).
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type KnexQb = any;
 
/**
 * dbTest seed — the organizational hierarchy the record-level ACL test
 * (`test.acl.flow`) asserts against: an organization with a head office and two
 * branches, a second organization nobody has a scope on, and the persons placed
 * in those units.  The ACL grants themselves live in
 * `accessAuthorizationMerge.yaml`, which also creates the role they mention.
 *
 * Every entity is created through `core_resource.ensure` (idempotent — an
 * existing row is returned untouched), and the hierarchy is written as
 * `core.triple` edges in one batch with a single `access_pathRefresh()`, so the
 * materialized `access.effectiveRole` / `access.effectiveScope` paths are
 * current before the tests run.
 *
 * Wire: `party.hierarchy.merge` — dispatched from
 * `meta/dbTest/9-partyHierarchyMerge.yaml` (the numeric prefix orders it after
 * the plain party/access seeds, which it depends on).
 *
 * Lives in `adapter/dbTest/` because it is a TEST-ONLY seed: the db adapter
 * imports `.dbTest` handler groups only under `dev`, never in production.
 */
export default handler(
    ({
        handler: {
            'db/coreResourceEnsure': coreResourceEnsure,
            'db/coreTripleMerge': coreTripleMerge,
        },
    }) =>
        async function partyHierarchyMerge(
            params: {
                /** Organization names (short names — `legalName` is mirrored onto the resource). */
                organizations?: string[];
                units?: Array<{
                    name: string;
                    unitType?: string;
                    /** Owning organization name → `unit --belongsTo--> organization`. */
                    organization?: string;
                    /** Parent unit name → `unit --isPartOf--> parentUnit`. */
                    parentUnit?: string;
                }>;
                persons?: Array<{
                    /** Resource name (the display label). */
                    name: string;
                    firstName: string;
                    lastName: string;
                    /** Membership → `person --belongsTo--> unit`. */
                    unit?: string;
                }>;
            },
            $meta: IMeta,
        ): Promise<{success: boolean}> {
            const qb: KnexQb = this.config?.context?.queryBuilder;
            if (!qb) throw new Error('Database not available');
 
            const triples: Array<{
                subjectId: string;
                predicateName: string;
                objectId: string;
            }> = [];
            /** resource names are resolved per type so a name may repeat across types. */
            const ids = new Map<string, string>();
            const key = (typeAlias: string, name: string): string => `${typeAlias}:${name}`;
 
            /** Ensure a resource + entity row and remember its id for the edges. */
            const ensure = async (
                typeAlias: string,
                table: string,
                keyName: string,
                name: string,
                extraColumns: Record<string, unknown>,
            ): Promise<string> => {
                const {resourceId} = await coreResourceEnsure<{resourceId: string}>(
                    {name, typeAlias, table, extraColumns, keyName},
                    $meta,
                );
                ids.set(key(typeAlias, name), resourceId);
                return resourceId;
            };
 
            /** The id of an entity ensured earlier in this seed. */
            const idOf = (typeAlias: string, name: string): string => {
                const id = ids.get(key(typeAlias, name));
                if (!id) throw new Error(`ACL seed references an unknown ${typeAlias} "${name}"`);
                return id;
            };
 
            for (const name of params.organizations ?? []) {
                await ensure('party.organization', 'party_organization', 'organizationId', name, {
                    legalName: name,
                });
            }
 
            // Pass 1 — every unit, so a child may be declared before its parent.
            for (const unit of params.units ?? []) {
                await ensure('party.unit', 'party_unit', 'unitId', unit.name, {
                    unitName: unit.name,
                    unitType: unit.unitType ?? null,
                });
            }
            // Pass 2 — the unit hierarchy.
            for (const unit of params.units ?? []) {
                if (unit.organization) {
                    triples.push({
                        subjectId: idOf('party.unit', unit.name),
                        predicateName: 'belongsTo',
                        objectId: idOf('party.organization', unit.organization),
                    });
                }
                if (unit.parentUnit) {
                    triples.push({
                        subjectId: idOf('party.unit', unit.name),
                        predicateName: 'isPartOf',
                        objectId: idOf('party.unit', unit.parentUnit),
                    });
                }
            }
 
            for (const person of params.persons ?? []) {
                const personId = await ensure(
                    'party.person',
                    'party_person',
                    'personId',
                    person.name,
                    {firstName: person.firstName, lastName: person.lastName},
                );
                if (person.unit) {
                    triples.push({
                        subjectId: personId,
                        predicateName: 'belongsTo',
                        objectId: idOf('party.unit', person.unit),
                    });
                }
            }
 
            // One path refresh for the whole batch (deferred per merge).
            await coreTripleMerge({triples, refreshPath: true}, $meta);
            return {success: true};
        },
);