Skip to content

Commit ebd9895

Browse files
committed
Update tests for feature resolution
1 parent 5fe6a87 commit ebd9895

File tree

2 files changed

+181
-6
lines changed

2 files changed

+181
-6
lines changed

packages/typespec-azure-resource-manager/src/resource.ts

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,7 +1294,7 @@ export function resolveResourceBaseType(type?: string | undefined): ResourceBase
12941294

12951295
export const [getResourceFeature, setResourceFeature] = useStateMap<
12961296
Model | Interface | Namespace,
1297-
string
1297+
EnumMember
12981298
>(ArmStateKeys.armFeature);
12991299

13001300
export const [getResourceFeatureSet, setResourceFeatureSet] = useStateMap<
@@ -1307,6 +1307,11 @@ export const [getResourceFeatureOptions, setResourceFeatureOptions] = useStateMa
13071307
ArmFeatureOptions
13081308
>(ArmStateKeys.armFeatureOptions);
13091309

1310+
const commonFeatureOptions: ArmFeatureOptions = {
1311+
featureName: "Common",
1312+
fileName: "common",
1313+
description: "",
1314+
};
13101315
export function getFeatureOptions(program: Program, feature: EnumMember): ArmFeatureOptions {
13111316
const defaultFeatureName: string = (feature.value ?? feature.name) as string;
13121317
const defaultOptions: ArmFeatureOptions = {
@@ -1317,14 +1322,74 @@ export function getFeatureOptions(program: Program, feature: EnumMember): ArmFea
13171322
return program.stateMap(ArmStateKeys.armFeatureOptions).get(feature) ?? defaultOptions;
13181323
}
13191324

1325+
/**
1326+
* Get the FeatureOptions for a given type, these could be inherited from the namespace or parent type
1327+
* @param program - The program to process.
1328+
* @param entity - The type entity to get feature options for.
1329+
* @returns The ArmFeatureOptions if found, otherwise undefined.
1330+
*/
1331+
export function getFeature(program: Program, entity: Type): ArmFeatureOptions {
1332+
switch (entity.kind) {
1333+
case "Namespace": {
1334+
const feature = getResourceFeature(program, entity);
1335+
if (feature === undefined) return commonFeatureOptions;
1336+
const options = getFeatureOptions(program, feature);
1337+
return options;
1338+
}
1339+
case "Interface":
1340+
case "Model": {
1341+
let feature = getResourceFeature(program, entity);
1342+
if (feature !== undefined) return getFeatureOptions(program, feature);
1343+
const namespace = entity.namespace;
1344+
if (namespace === undefined) return commonFeatureOptions;
1345+
feature = getResourceFeature(program, namespace);
1346+
if (feature === undefined) return commonFeatureOptions;
1347+
return getFeatureOptions(program, feature);
1348+
}
1349+
case "Operation": {
1350+
const opInterface = entity.interface;
1351+
if (opInterface !== undefined) {
1352+
return getFeature(program, opInterface);
1353+
}
1354+
const namespace = entity.namespace;
1355+
if (namespace === undefined) return commonFeatureOptions;
1356+
const feature = getResourceFeature(program, namespace);
1357+
if (feature === undefined) return commonFeatureOptions;
1358+
return getFeatureOptions(program, feature);
1359+
}
1360+
case "EnumMember": {
1361+
return getFeature(program, entity.enum);
1362+
}
1363+
case "UnionVariant": {
1364+
return getFeature(program, entity.union);
1365+
}
1366+
case "ModelProperty": {
1367+
if (entity.model === undefined) return commonFeatureOptions;
1368+
return getFeature(program, entity.model);
1369+
}
1370+
case "Enum":
1371+
case "Union":
1372+
case "Scalar": {
1373+
const namespace = entity.namespace;
1374+
if (namespace === undefined) return commonFeatureOptions;
1375+
const feature = getResourceFeature(program, namespace);
1376+
if (feature === undefined) return commonFeatureOptions;
1377+
return getFeatureOptions(program, feature);
1378+
}
1379+
1380+
default:
1381+
return commonFeatureOptions;
1382+
}
1383+
}
1384+
13201385
export const $feature: FeatureDecorator = (
13211386
context: DecoratorContext,
13221387
entity: Model | Interface | Namespace,
13231388
featureName: EnumMember,
13241389
) => {
13251390
const { program } = context;
13261391
const options = getFeatureOptions(program, featureName);
1327-
setResourceFeature(program, entity, options.featureName);
1392+
setResourceFeature(program, entity, featureName);
13281393
};
13291394

13301395
export const $features: FeaturesDecorator = (

packages/typespec-azure-resource-manager/test/resource.test.ts

Lines changed: 114 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ArmLifecycleOperationKind } from "../src/operations.js";
77
import {
88
ArmResourceDetails,
99
getArmResources,
10+
getFeature,
1011
getResourceFeature,
1112
getResourceFeatureSet,
1213
} from "../src/resource.js";
@@ -517,9 +518,9 @@ enum Features {
517518
});
518519

519520
const fooFeature = getResourceFeature(result.program, result.FooResource);
520-
expect(fooFeature).toMatch("FeatureA");
521+
expect(fooFeature?.name).toMatch("FeatureA");
521522
const barFeature = getResourceFeature(result.program, result.BarResource);
522-
expect(barFeature).toMatch("FeatureB");
523+
expect(barFeature?.name).toMatch("FeatureB");
523524
});
524525
it("allows customizing features and feature options", async () => {
525526
const [result, diagnostics] = await Tester.compileAndDiagnose(t.code`
@@ -575,9 +576,118 @@ enum Features {
575576
});
576577

577578
const fooFeature = getResourceFeature(result.program, result.FooResource);
578-
expect(fooFeature).toMatch("FeatureA");
579+
expect(fooFeature?.name).toMatch("FeatureA");
579580
const barFeature = getResourceFeature(result.program, result.BarResource);
580-
expect(barFeature).toMatch("FeatureB");
581+
expect(barFeature?.name).toMatch("FeatureB");
582+
});
583+
it("reports correct features for child types", async () => {
584+
const [result, diagnostics] = await Tester.compileAndDiagnose(t.code`
585+
586+
@Azure.ResourceManager.Legacy.features(Features)
587+
@versioned(Versions)
588+
@armProviderNamespace("Microsoft.Test")
589+
namespace ${t.namespace("MSTest")};
590+
/** Contoso API versions */
591+
enum Versions {
592+
/** 2021-10-01-preview version */
593+
v2025_11_19_preview: "2025-11-19-preview",
594+
}
595+
enum Features {
596+
/** Feature A */
597+
@Azure.ResourceManager.Legacy.featureOptions(#{featureName: "FeatureA", fileName: "feature-a", description: "The data for feature A"})
598+
FeatureA: "Feature A",
599+
/** Feature B */
600+
@Azure.ResourceManager.Legacy.featureOptions(#{featureName: "FeatureB", fileName: "feature-b", description: "The data for feature B"})
601+
FeatureB: "Feature B",
602+
}
603+
@secret
604+
scalar secretString extends string;
605+
606+
@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
607+
model ${t.model("FooResource")} is TrackedResource<FooResourceProperties> {
608+
...ResourceNameParameter<FooResource>;
609+
}
610+
611+
@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
612+
model ${t.model("FooResourceProperties")} {
613+
...DefaultProvisioningStateProperty;
614+
password: secretString;
615+
}
616+
617+
@Azure.ResourceManager.Legacy.feature(Features.FeatureB)
618+
model ${t.model("BarResource")} is ProxyResource<BarResourceProperties> {
619+
...ResourceNameParameter<BarResource>;
620+
}
621+
model ${t.model("BarResourceProperties")} {
622+
...DefaultProvisioningStateProperty;
623+
password: secretString;
624+
}
625+
626+
@Azure.ResourceManager.Legacy.feature(Features.FeatureA)
627+
@armResourceOperations
628+
interface ${t.interface("Foos")} extends Azure.ResourceManager.TrackedResourceOperations<FooResource, FooResourceProperties> {}
629+
630+
@Azure.ResourceManager.Legacy.feature(Features.FeatureB)
631+
@armResourceOperations
632+
interface ${t.interface("Bars")} extends Azure.ResourceManager.TrackedResourceOperations<BarResource, BarResourceProperties> {}
633+
`);
634+
const featureAObject = {
635+
featureName: "FeatureA",
636+
fileName: "feature-a",
637+
description: "The data for feature A",
638+
};
639+
const featureBObject = {
640+
featureName: "FeatureB",
641+
fileName: "feature-b",
642+
description: "The data for feature B",
643+
};
644+
645+
const defaultObject = {
646+
featureName: "Common",
647+
fileName: "common",
648+
description: "",
649+
};
650+
expectDiagnosticEmpty(diagnostics);
651+
const features = getResourceFeatureSet(result.program, result.MSTest);
652+
expect(features).toBeDefined();
653+
ok(features);
654+
const keys = Array.from(features.keys());
655+
expect(keys).toEqual(["FeatureA", "FeatureB"]);
656+
expect(features?.get("FeatureA")).toEqual(featureAObject);
657+
expect(features?.get("FeatureB")).toEqual(featureBObject);
658+
659+
const fooFeature = getFeature(result.program, result.FooResource);
660+
expect(fooFeature).toMatchObject(featureAObject);
661+
const fooPropertiesFeature = getFeature(result.program, result.FooResourceProperties);
662+
expect(fooPropertiesFeature).toMatchObject(featureAObject);
663+
const fooPasswordProperty = result.FooResourceProperties.properties.get("password");
664+
expect(fooPasswordProperty).toBeDefined();
665+
const fooPasswordFeature = getFeature(result.program, fooPasswordProperty!);
666+
expect(fooPasswordFeature).toMatchObject(featureAObject);
667+
const fooPasswordTypeFeature = getFeature(result.program, fooPasswordProperty!.type);
668+
expect(fooPasswordTypeFeature).toMatchObject(defaultObject);
669+
const foosFeature = getFeature(result.program, result.Foos);
670+
expect(foosFeature).toMatchObject(featureAObject);
671+
for (const op of [...result.Foos.operations.values()]) {
672+
const opFeature = getFeature(result.program, op);
673+
expect(opFeature).toMatchObject(featureAObject);
674+
}
675+
const barFeature = getFeature(result.program, result.BarResource);
676+
expect(barFeature).toMatchObject(featureBObject);
677+
const barPropertiesFeature = getFeature(result.program, result.BarResourceProperties);
678+
expect(barPropertiesFeature).toMatchObject(defaultObject);
679+
const barPasswordProperty = result.BarResourceProperties.properties.get("password");
680+
expect(barPasswordProperty).toBeDefined();
681+
const barPasswordFeature = getFeature(result.program, barPasswordProperty!);
682+
expect(barPasswordFeature).toMatchObject(defaultObject);
683+
const barPasswordTypeFeature = getFeature(result.program, barPasswordProperty!.type);
684+
expect(barPasswordTypeFeature).toMatchObject(defaultObject);
685+
const barsFeature = getFeature(result.program, result.Bars);
686+
expect(barsFeature).toMatchObject(featureBObject);
687+
for (const op of [...result.Bars.operations.values()]) {
688+
const opFeature = getFeature(result.program, op);
689+
expect(opFeature).toMatchObject(featureBObject);
690+
}
581691
});
582692
});
583693
describe("network security perimeter", () => {

0 commit comments

Comments
 (0)