import { describe, test, expect } from "vitest"; import { resolveAttrRefs } from "./resolve"; import { AttrRef } from "../attrref"; import type { Declarable } from "../declarable"; import { DECLARABLE_MARKER } from "../declarable"; import { LOGICAL_NAME_SYMBOL, getLogicalName } from "../utils"; describe("resolveAttrRefs", () => { test("sets logical names on all entities", () => { const entity1: Declarable = { lexicon: "test", entityType: "Test1", [DECLARABLE_MARKER]: true, }; const entity2: Declarable = { lexicon: "test", entityType: "Test2", [DECLARABLE_MARKER]: true, }; const entities = new Map([ ["MyEntity1", entity1], ["MyEntity2", entity2], ]); resolveAttrRefs(entities); expect(getLogicalName(entity1)).toBe("MyEntity1"); expect(getLogicalName(entity2)).toBe("MyEntity2"); }); test("resolves AttrRef with parent in entities collection", () => { const parent: Declarable = { lexicon: "test", entityType: "Parent", [DECLARABLE_MARKER]: true, }; const child: Declarable & { ref: AttrRef } = { lexicon: "test", entityType: "Child", [DECLARABLE_MARKER]: true, ref: new AttrRef(parent, "Arn"), }; const entities = new Map([ ["ParentResource", parent], ["ChildResource", child], ]); resolveAttrRefs(entities); // Verify the AttrRef can now serialize expect(child.ref.toJSON()).toEqual({ __attrRef: { entity: "ParentResource", attribute: "Arn" }, }); }); test("resolves multiple AttrRefs on same entity", () => { const parent: Declarable = { lexicon: "test", entityType: "Parent", [DECLARABLE_MARKER]: true, }; const child: Declarable & { arn: AttrRef; name: AttrRef } = { lexicon: "test", entityType: "Child", [DECLARABLE_MARKER]: true, arn: new AttrRef(parent, "Arn"), name: new AttrRef(parent, "Name"), }; const entities = new Map([ ["ParentResource", parent], ["ChildResource", child], ]); resolveAttrRefs(entities); expect(child.arn.toJSON()).toEqual({ __attrRef: { entity: "ParentResource", attribute: "Arn" }, }); expect(child.name.toJSON()).toEqual({ __attrRef: { entity: "ParentResource", attribute: "Name" }, }); }); test("resolves AttrRefs with different parents", () => { const parent1: Declarable = { lexicon: "test", entityType: "Parent1", [DECLARABLE_MARKER]: true, }; const parent2: Declarable = { lexicon: "test", entityType: "Parent2", [DECLARABLE_MARKER]: true, }; const child: Declarable & { ref1: AttrRef; ref2: AttrRef } = { lexicon: "test", entityType: "Child", [DECLARABLE_MARKER]: true, ref1: new AttrRef(parent1, "Arn"), ref2: new AttrRef(parent2, "Name"), }; const entities = new Map([ ["Parent1Resource", parent1], ["Parent2Resource", parent2], ["ChildResource", child], ]); resolveAttrRefs(entities); expect(child.ref1.toJSON()).toEqual({ __attrRef: { entity: "Parent1Resource", attribute: "Arn" }, }); expect(child.ref2.toJSON()).toEqual({ __attrRef: { entity: "Parent2Resource", attribute: "Name" }, }); }); test("throws when AttrRef parent not in entities collection", () => { const parent = {}; // Not a Declarable, not in entities const child: Declarable & { ref: AttrRef } = { lexicon: "test", entityType: "Child", [DECLARABLE_MARKER]: true, ref: new AttrRef(parent, "Arn"), }; const entities = new Map([["ChildResource", child]]); expect(() => resolveAttrRefs(entities)).toThrow( 'Cannot resolve AttrRef on "ChildResource.ref": parent entity not found in entities collection' ); }); test("handles entities with no AttrRefs", () => { const entity1: Declarable = { lexicon: "test", entityType: "Test1", [DECLARABLE_MARKER]: true, }; const entity2: Declarable & { prop: string } = { lexicon: "test", entityType: "Test2", [DECLARABLE_MARKER]: true, prop: "value", }; const entities = new Map([ ["Entity1", entity1], ["Entity2", entity2], ]); expect(() => resolveAttrRefs(entities)).not.toThrow(); expect(getLogicalName(entity1)).toBe("Entity1"); expect(getLogicalName(entity2)).toBe("Entity2"); }); test("handles empty entities map", () => { const entities = new Map(); expect(() => resolveAttrRefs(entities)).not.toThrow(); }); test("resolves chain of entities with AttrRefs", () => { const root: Declarable = { lexicon: "test", entityType: "Root", [DECLARABLE_MARKER]: true, }; const middle: Declarable & { rootRef: AttrRef } = { lexicon: "test", entityType: "Middle", [DECLARABLE_MARKER]: true, rootRef: new AttrRef(root, "Id"), }; const leaf: Declarable & { middleRef: AttrRef } = { lexicon: "test", entityType: "Leaf", [DECLARABLE_MARKER]: true, middleRef: new AttrRef(middle, "Name"), }; const entities = new Map([ ["RootResource", root], ["MiddleResource", middle], ["LeafResource", leaf], ]); resolveAttrRefs(entities); expect(middle.rootRef.toJSON()).toEqual({ __attrRef: { entity: "RootResource", attribute: "Id" }, }); expect(leaf.middleRef.toJSON()).toEqual({ __attrRef: { entity: "MiddleResource", attribute: "Name" }, }); }); test("handles entity referencing itself", () => { const entity: Declarable & { selfRef: AttrRef } = { lexicon: "test", entityType: "SelfReferencing", [DECLARABLE_MARKER]: true, selfRef: null as unknown as AttrRef, // Will be set below }; entity.selfRef = new AttrRef(entity, "Arn"); const entities = new Map([["MyResource", entity]]); resolveAttrRefs(entities); expect(entity.selfRef.toJSON()).toEqual({ __attrRef: { entity: "MyResource", attribute: "Arn" }, }); }); test("preserves logical name symbol on entities", () => { const entity: Declarable = { lexicon: "test", entityType: "Test", [DECLARABLE_MARKER]: true, }; const entities = new Map([["TestResource", entity]]); resolveAttrRefs(entities); const entityWithSymbol = entity as unknown as Record; expect(entityWithSymbol[LOGICAL_NAME_SYMBOL]).toBe("TestResource"); }); test("uses export name as logical name", () => { const entity: Declarable = { lexicon: "test", entityType: "Test", [DECLARABLE_MARKER]: true, }; const entities = new Map([ ["MyCustomExportName", entity], ]); resolveAttrRefs(entities); expect(getLogicalName(entity)).toBe("MyCustomExportName"); }); test("handles complex attribute names in AttrRef", () => { const parent: Declarable = { lexicon: "test", entityType: "Parent", [DECLARABLE_MARKER]: true, }; const child: Declarable & { ref: AttrRef } = { lexicon: "test", entityType: "Child", [DECLARABLE_MARKER]: true, ref: new AttrRef(parent, "Outputs.WebsiteURL"), }; const entities = new Map([ ["ParentResource", parent], ["ChildResource", child], ]); resolveAttrRefs(entities); expect(child.ref.toJSON()).toEqual({ __attrRef: { entity: "ParentResource", attribute: "Outputs.WebsiteURL" }, }); }); });