@intentius/chant-lexicon-aws 0.34.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/read-client.d.ts +108 -0
- package/dist/api/read-client.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts.map +1 -1
- package/dist/composites/lambda-function.d.ts +4 -4
- package/dist/composites/lambda-function.d.ts.map +1 -1
- package/dist/deep-observe.d.ts +87 -26
- package/dist/deep-observe.d.ts.map +1 -1
- package/dist/dependencies.d.ts +3 -0
- package/dist/dependencies.d.ts.map +1 -1
- package/dist/generated/index.d.ts +1 -0
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +4 -4
- package/dist/manifest.json +1 -1
- package/dist/meta.json +30 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/properties.d.ts +23 -7
- package/dist/properties.d.ts.map +1 -1
- package/dist/types/index.d.ts +21 -0
- package/package.json +2 -2
- package/src/api/read-client.test.ts +203 -0
- package/src/api/read-client.ts +286 -0
- package/src/codegen/docs.ts +21 -4
- package/src/deep-observe.test.ts +357 -99
- package/src/deep-observe.ts +347 -131
- package/src/dependencies.ts +11 -6
- package/src/generated/index.d.ts +21 -0
- package/src/generated/index.ts +1 -0
- package/src/generated/lexicon-aws.json +30 -0
- package/src/index.ts +18 -1
- package/src/lifecycle-integration.test.ts +54 -45
- package/src/plugin.ts +66 -55
- package/src/properties.test.ts +38 -5
- package/src/properties.ts +47 -21
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,8 @@ export type { StackOutput } from "@intentius/chant/stack-output";
|
|
|
13
13
|
export { output, isLexiconOutput } from "@intentius/chant/lexicon-output";
|
|
14
14
|
export type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
15
15
|
export { awsPlugin } from "./plugin.js";
|
|
16
|
-
export { observeResourcesDeepAws, awsDeepNormalizationHooks,
|
|
16
|
+
export { observeResourcesDeepAws, awsDeepNormalizationHooks, hasOwnershipMarker, DEEP_READABLE_TYPES, AWS_READ_ONLY_NAMES, AWS_SERVICE_DEFAULTS, } from "./deep-observe.js";
|
|
17
|
+
export { describeStackResources, describeStackOutputs, getResource, listResources, parseResourceDescription, AwsReadError, type AwsReadHttp, type AwsReadClientOptions, type CloudControlDescription, type StackResource, } from "./api/read-client.js";
|
|
17
18
|
export { Sub, Ref, GetAtt, If, Join, Select, Split, Base64, GetAZs, SubIntrinsic, RefIntrinsic, GetAttIntrinsic, IfIntrinsic, JoinIntrinsic, SelectIntrinsic, SplitIntrinsic, Base64Intrinsic, GetAZsIntrinsic, } from "./intrinsics.js";
|
|
18
19
|
export { AWS, StackName, Region, AccountId, StackId, URLSuffix, NoValue, NotificationARNs, Partition, } from "./pseudo.js";
|
|
19
20
|
export * from "./generated/index.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACjF,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACzG,YAAY,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG7C,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACvI,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACtF,YAAY,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AAChG,YAAY,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAC1E,YAAY,EAAE,aAAa,EAAE,MAAM,iCAAiC,CAAC;AAGrE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAIrC,OAAO,EACL,uBAAuB,EACvB,yBAAyB,EACzB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/integrity.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
5
|
-
"meta.json": "
|
|
6
|
-
"types/index.d.ts": "
|
|
4
|
+
"manifest.json": "39b9cb89de0be79ec9f96b84e9ab60369410b4ca7562b4aba91116ac75443ae1",
|
|
5
|
+
"meta.json": "f430880414df1288383dc91c313e50ecab26d8e7bcce520bf894eeb89b220e9f",
|
|
6
|
+
"types/index.d.ts": "4d6f07139586f3d7fc4d9ea0756161587709a39e21f20fbdb05e31f32a44f1c1",
|
|
7
7
|
"rules/hardcoded-region.ts": "5a0eaf7ab391231fe6cd51426ece29539cb4b36f31c8dd060956638fed55722a",
|
|
8
8
|
"rules/iam-wildcard.ts": "135d7217d278fef50939e605c5da32603ba35674b8c4b5c4d66a06c77903e945",
|
|
9
9
|
"rules/s3-encryption.ts": "c4f9f8aa8dc382ed98c7b04e820a600590cc75335a8db2e087cbfbfe4003d09e",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"skills/chant-aws-eks.md": "8789255709ff004ad0a875fd5999edcdc66fc6e33d710db058d9f42703bcfdfe",
|
|
60
60
|
"skills/chant-aws-carve-terraform.md": "f4c6fe1c702250665f90d82b3bdaba5ea50ae75e380a8ae321dad6eb1c39683e"
|
|
61
61
|
},
|
|
62
|
-
"composite": "
|
|
62
|
+
"composite": "ad1f16796cbb0c159d1d7f525c199b3229ac4f427508b0ad4a22cb0ea30a324b"
|
|
63
63
|
}
|
package/dist/manifest.json
CHANGED
package/dist/meta.json
CHANGED
|
@@ -91208,6 +91208,36 @@
|
|
|
91208
91208
|
"kind": "property",
|
|
91209
91209
|
"lexicon": "aws"
|
|
91210
91210
|
},
|
|
91211
|
+
"ServiceSetting": {
|
|
91212
|
+
"resourceType": "AWS::SSM::ServiceSetting",
|
|
91213
|
+
"kind": "resource",
|
|
91214
|
+
"lexicon": "aws",
|
|
91215
|
+
"attrs": {
|
|
91216
|
+
"Status": "Status",
|
|
91217
|
+
"LastModifiedDate": "LastModifiedDate",
|
|
91218
|
+
"LastModifiedUser": "LastModifiedUser",
|
|
91219
|
+
"Arn": "Arn"
|
|
91220
|
+
},
|
|
91221
|
+
"propertyConstraints": {
|
|
91222
|
+
"SettingId": {
|
|
91223
|
+
"minLength": 1,
|
|
91224
|
+
"maxLength": 1000
|
|
91225
|
+
},
|
|
91226
|
+
"SettingValue": {
|
|
91227
|
+
"minLength": 1,
|
|
91228
|
+
"maxLength": 4096
|
|
91229
|
+
},
|
|
91230
|
+
"LastModifiedDate": {
|
|
91231
|
+
"format": "date-time"
|
|
91232
|
+
}
|
|
91233
|
+
},
|
|
91234
|
+
"createOnly": [
|
|
91235
|
+
"SettingId"
|
|
91236
|
+
],
|
|
91237
|
+
"primaryIdentifier": [
|
|
91238
|
+
"Arn"
|
|
91239
|
+
]
|
|
91240
|
+
},
|
|
91211
91241
|
"ServiceSoftwareOptions": {
|
|
91212
91242
|
"resourceType": "AWS::OpenSearchService::Domain.ServiceSoftwareOptions",
|
|
91213
91243
|
"kind": "property",
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAgL,MAAM,0BAA0B,CAAC;AAoC5O;yEACyE;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAWnD,eAAO,MAAM,SAAS,EAAE,aAu5BvB,CAAC"}
|
package/dist/properties.d.ts
CHANGED
|
@@ -34,15 +34,31 @@ import type { ResourceMetadata } from "@intentius/chant/lexicon";
|
|
|
34
34
|
export declare function stampRegion(resources: Record<string, ResourceMetadata>, region?: string): Record<string, ResourceMetadata>;
|
|
35
35
|
/** True when this lexicon can read the kind's own properties. */
|
|
36
36
|
export declare function canDescribe(kind: string): boolean;
|
|
37
|
+
/** What an enrichment pass managed to do, so a caller can tell a miss from a no-op. */
|
|
38
|
+
export interface OwnPropertiesResult {
|
|
39
|
+
resources: Record<string, ResourceMetadata>;
|
|
40
|
+
/**
|
|
41
|
+
* True when enrichment was attempted and *every* kind's read failed — the
|
|
42
|
+
* transport is unavailable rather than the account being quiet. Callers must
|
|
43
|
+
* not present the result as a complete observation (#1089).
|
|
44
|
+
*/
|
|
45
|
+
transportFailed: boolean;
|
|
46
|
+
/** Why each failed kind failed, keyed by CloudFormation type, so a caller can attribute per resource. */
|
|
47
|
+
failures: Map<string, string>;
|
|
48
|
+
}
|
|
37
49
|
/**
|
|
38
50
|
* Merge each resource's own properties into an observation, in place of nothing.
|
|
39
51
|
*
|
|
40
|
-
* Best-effort per kind
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
52
|
+
* Best-effort per kind: an endpoint that cannot answer `describe-subnets` still
|
|
53
|
+
* yields instance properties. A miss costs detail — with one exception that used
|
|
54
|
+
* to be silent and is now reported.
|
|
55
|
+
*
|
|
56
|
+
* The exception (#1206): these attributes are compared by `lifecycle diff`, so a
|
|
57
|
+
* read that fails after the resource is already identified does not degrade to
|
|
58
|
+
* "less detail", it degrades to *drift* — the snapshot has `GroupId`, the live
|
|
59
|
+
* read has nothing, and the differ reports the property as removed. A partial
|
|
60
|
+
* failure still rides the best-effort path, but a total one is a hole the caller
|
|
61
|
+
* has to declare rather than a thinner set of facts.
|
|
46
62
|
*/
|
|
47
|
-
export declare function describeOwnProperties(resources: Record<string, ResourceMetadata>, region?: string): Promise<
|
|
63
|
+
export declare function describeOwnProperties(resources: Record<string, ResourceMetadata>, region?: string): Promise<OwnPropertiesResult>;
|
|
48
64
|
//# sourceMappingURL=properties.d.ts.map
|
package/dist/properties.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../src/properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAkCjE;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC3C,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAUlC;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED
|
|
1
|
+
{"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../src/properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAkCjE;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC3C,MAAM,CAAC,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAUlC;AAED,iEAAiE;AACjE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,uFAAuF;AACvF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC5C;;;;OAIG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,yGAAyG;IACzG,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EAC3C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAyE9B"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -32891,6 +32891,27 @@ export declare class ServiceProfile {
|
|
|
32891
32891
|
readonly LoRaWAN_UlRatePolicy: string;
|
|
32892
32892
|
}
|
|
32893
32893
|
|
|
32894
|
+
export declare class ServiceSetting {
|
|
32895
|
+
constructor(props: {
|
|
32896
|
+
/** The ID of the service setting, such as /ssm/parameter-store/high-throughput-enabled. */
|
|
32897
|
+
SettingId: string;
|
|
32898
|
+
/** The value of the service setting. */
|
|
32899
|
+
SettingValue: string;
|
|
32900
|
+
/** The ARN of the service setting. */
|
|
32901
|
+
Arn?: string;
|
|
32902
|
+
/** The last time the service setting was modified. */
|
|
32903
|
+
LastModifiedDate?: string;
|
|
32904
|
+
/** The ARN of the last modified user. */
|
|
32905
|
+
LastModifiedUser?: string;
|
|
32906
|
+
/** The status of the service setting. The value can be Default, Customized or PendingUpdate. */
|
|
32907
|
+
Status?: string;
|
|
32908
|
+
}, attributes?: CFResourceAttributes);
|
|
32909
|
+
readonly Arn: string;
|
|
32910
|
+
readonly LastModifiedDate: string;
|
|
32911
|
+
readonly LastModifiedUser: string;
|
|
32912
|
+
readonly Status: string;
|
|
32913
|
+
}
|
|
32914
|
+
|
|
32894
32915
|
export declare class ServiceTemplate {
|
|
32895
32916
|
constructor(props: {
|
|
32896
32917
|
/** <p>The Amazon Resource Name (ARN) of the service template.</p> */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.
|
|
83
|
+
"@intentius/chant": "^0.37.0",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The native AWS read transport (#1206).
|
|
3
|
+
*
|
|
4
|
+
* The transport is injected everywhere, so nothing here reaches a network. The
|
|
5
|
+
* XML cases are the ones worth being careful about: the applier's `xmlField`
|
|
6
|
+
* only ever handled flat scalars, and the read path needs `<member>` lists.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, test, expect } from "vitest";
|
|
9
|
+
import {
|
|
10
|
+
AwsReadError,
|
|
11
|
+
describeStackOutputs,
|
|
12
|
+
describeStackResources,
|
|
13
|
+
getResource,
|
|
14
|
+
listResources,
|
|
15
|
+
parseResourceDescription,
|
|
16
|
+
serviceUrl,
|
|
17
|
+
xmlLeaves,
|
|
18
|
+
xmlMembers,
|
|
19
|
+
xmlError,
|
|
20
|
+
type AwsReadHttp,
|
|
21
|
+
} from "./read-client";
|
|
22
|
+
|
|
23
|
+
const respond = (text: string, status = 200): ReturnType<AwsReadHttp> => Promise.resolve({ status, text });
|
|
24
|
+
|
|
25
|
+
/** Records every call so a test can assert what went on the wire. */
|
|
26
|
+
function recording(handler: (body: string, headers: Record<string, string>) => ReturnType<AwsReadHttp>) {
|
|
27
|
+
const calls: Array<{ url: string; headers: Record<string, string>; body: string }> = [];
|
|
28
|
+
const http: AwsReadHttp = (url, init) => {
|
|
29
|
+
calls.push({ url, headers: init.headers, body: init.body });
|
|
30
|
+
return handler(init.body, init.headers);
|
|
31
|
+
};
|
|
32
|
+
return { http, calls };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const stackXml = `
|
|
36
|
+
<DescribeStackResourcesResponse><DescribeStackResourcesResult><StackResources>
|
|
37
|
+
<member>
|
|
38
|
+
<LogicalResourceId>dataBucket</LogicalResourceId>
|
|
39
|
+
<ResourceType>AWS::S3::Bucket</ResourceType>
|
|
40
|
+
<PhysicalResourceId>acme-assets</PhysicalResourceId>
|
|
41
|
+
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
|
|
42
|
+
<Timestamp>2026-01-01T00:00:00Z</Timestamp>
|
|
43
|
+
</member>
|
|
44
|
+
<member>
|
|
45
|
+
<LogicalResourceId>vpc</LogicalResourceId>
|
|
46
|
+
<ResourceType>AWS::EC2::VPC</ResourceType>
|
|
47
|
+
<PhysicalResourceId>vpc-01</PhysicalResourceId>
|
|
48
|
+
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
|
|
49
|
+
</member>
|
|
50
|
+
</StackResources></DescribeStackResourcesResult></DescribeStackResourcesResponse>`;
|
|
51
|
+
|
|
52
|
+
describe("serviceUrl", () => {
|
|
53
|
+
test("an override wins over the regional host, with exactly one trailing slash", () => {
|
|
54
|
+
expect(serviceUrl("cloudformation", "http://localhost:4566")).toBe("http://localhost:4566/");
|
|
55
|
+
expect(serviceUrl("cloudformation", "http://localhost:4566/")).toBe("http://localhost:4566/");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("without an override it is the real regional host for that service", () => {
|
|
59
|
+
expect(serviceUrl("cloudcontrolapi", undefined, "eu-west-1")).toBe("https://cloudcontrolapi.eu-west-1.amazonaws.com/");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("the Query XML shapes", () => {
|
|
64
|
+
test("xmlLeaves flattens one fragment and decodes entities", () => {
|
|
65
|
+
expect(xmlLeaves("<A>1</A><B>x & y</B>")).toEqual({ A: "1", B: "x & y" });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("xmlLeaves keeps the first of a repeated tag", () => {
|
|
69
|
+
expect(xmlLeaves("<A>first</A><A>second</A>")).toEqual({ A: "first" });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("xmlMembers reads a member list, which xmlField never could", () => {
|
|
73
|
+
expect(xmlMembers(stackXml, "StackResources")).toHaveLength(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("xmlMembers is empty for an absent list rather than throwing", () => {
|
|
77
|
+
expect(xmlMembers("<Response/>", "StackResources")).toEqual([]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("xmlError finds a Query error document, and nothing in a success body", () => {
|
|
81
|
+
expect(xmlError("<ErrorResponse><Error><Code>ValidationError</Code><Message>nope</Message></Error></ErrorResponse>")).toEqual({
|
|
82
|
+
code: "ValidationError",
|
|
83
|
+
message: "nope",
|
|
84
|
+
});
|
|
85
|
+
expect(xmlError(stackXml)).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("describeStackResources", () => {
|
|
90
|
+
test("maps members onto the read path's shape, keeping optional fields optional", async () => {
|
|
91
|
+
const { http, calls } = recording(() => respond(stackXml));
|
|
92
|
+
const resources = await describeStackResources("local", { endpoint: "http://localhost:4566", http });
|
|
93
|
+
|
|
94
|
+
expect(resources).toEqual([
|
|
95
|
+
{
|
|
96
|
+
logicalId: "dataBucket",
|
|
97
|
+
type: "AWS::S3::Bucket",
|
|
98
|
+
physicalId: "acme-assets",
|
|
99
|
+
status: "CREATE_COMPLETE",
|
|
100
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
101
|
+
},
|
|
102
|
+
{ logicalId: "vpc", type: "AWS::EC2::VPC", physicalId: "vpc-01", status: "CREATE_COMPLETE" },
|
|
103
|
+
]);
|
|
104
|
+
expect(calls[0].url).toBe("http://localhost:4566/");
|
|
105
|
+
expect(calls[0].body).toContain("Action=DescribeStackResources");
|
|
106
|
+
expect(calls[0].body).toContain("StackName=local");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("a Query error becomes a typed throw carrying the service's own code", async () => {
|
|
110
|
+
const { http } = recording(() =>
|
|
111
|
+
respond("<ErrorResponse><Error><Code>ValidationError</Code><Message>Stack with id local does not exist</Message></Error></ErrorResponse>", 400),
|
|
112
|
+
);
|
|
113
|
+
await expect(describeStackResources("local", { http })).rejects.toMatchObject({
|
|
114
|
+
name: "AwsReadError",
|
|
115
|
+
code: "ValidationError",
|
|
116
|
+
status: 400,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a non-2xx with no error document still throws rather than parsing as empty", async () => {
|
|
121
|
+
const { http } = recording(() => respond("<html>502</html>", 502));
|
|
122
|
+
await expect(describeStackResources("local", { http })).rejects.toBeInstanceOf(AwsReadError);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("describeStackOutputs", () => {
|
|
127
|
+
test("reads the output members as a flat record", async () => {
|
|
128
|
+
const { http } = recording(() =>
|
|
129
|
+
respond(`<DescribeStacksResponse><Outputs>
|
|
130
|
+
<member><OutputKey>VpcId</OutputKey><OutputValue>vpc-01</OutputValue></member>
|
|
131
|
+
<member><OutputKey>Empty</OutputKey></member>
|
|
132
|
+
</Outputs></DescribeStacksResponse>`),
|
|
133
|
+
);
|
|
134
|
+
expect(await describeStackOutputs("local", { http })).toEqual({ VpcId: "vpc-01", Empty: "" });
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe("Cloud Control", () => {
|
|
139
|
+
const description = (identifier: string, properties: Record<string, unknown>) => ({
|
|
140
|
+
Identifier: identifier,
|
|
141
|
+
Properties: JSON.stringify(properties),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("getResource unwraps the doubly-encoded model and targets the JSON 1.0 operation", async () => {
|
|
145
|
+
const { http, calls } = recording(() =>
|
|
146
|
+
respond(JSON.stringify({ ResourceDescription: description("acme-assets", { BucketName: "acme-assets" }) })),
|
|
147
|
+
);
|
|
148
|
+
expect(await getResource("AWS::S3::Bucket", "acme-assets", { http })).toEqual({
|
|
149
|
+
identifier: "acme-assets",
|
|
150
|
+
properties: { BucketName: "acme-assets" },
|
|
151
|
+
});
|
|
152
|
+
expect(calls[0].headers["x-amz-target"]).toBe("CloudApiService.GetResource");
|
|
153
|
+
expect(calls[0].headers["content-type"]).toBe("application/x-amz-json-1.0");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a modelled error becomes a typed throw — UnsupportedOperation is not a miss", async () => {
|
|
157
|
+
const { http } = recording(() =>
|
|
158
|
+
respond(JSON.stringify({ __type: "UnsupportedOperation", message: "Operation GetResource is not supported." }), 400),
|
|
159
|
+
);
|
|
160
|
+
await expect(getResource("AWS::S3::Bucket", "b", { http })).rejects.toMatchObject({
|
|
161
|
+
code: "UnsupportedOperation",
|
|
162
|
+
status: 400,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
test("a `__type` carrying a shape prefix is reported by its bare name", async () => {
|
|
167
|
+
const { http } = recording(() =>
|
|
168
|
+
respond(JSON.stringify({ __type: "com.amazonaws.cloudapi#ThrottlingException", message: "Rate exceeded" }), 400),
|
|
169
|
+
);
|
|
170
|
+
await expect(getResource("AWS::S3::Bucket", "b", { http })).rejects.toMatchObject({ code: "ThrottlingException" });
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("listResources follows NextToken to exhaustion", async () => {
|
|
174
|
+
let page = 0;
|
|
175
|
+
const { http, calls } = recording(() => {
|
|
176
|
+
page += 1;
|
|
177
|
+
return respond(
|
|
178
|
+
JSON.stringify(
|
|
179
|
+
page === 1
|
|
180
|
+
? { ResourceDescriptions: [description("a", { BucketName: "a" })], NextToken: "more" }
|
|
181
|
+
: { ResourceDescriptions: [description("b", { BucketName: "b" })] },
|
|
182
|
+
),
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
const all = await listResources("AWS::S3::Bucket", { http });
|
|
186
|
+
expect(all.map((r) => r.identifier)).toEqual(["a", "b"]);
|
|
187
|
+
expect(calls).toHaveLength(2);
|
|
188
|
+
expect(calls[1].body).toContain("more");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("an unparseable description is dropped, not returned as an empty resource", async () => {
|
|
192
|
+
const { http } = recording(() =>
|
|
193
|
+
respond(JSON.stringify({ ResourceDescriptions: [{ Identifier: "a", Properties: "{oops" }] })),
|
|
194
|
+
);
|
|
195
|
+
expect(await listResources("AWS::S3::Bucket", { http })).toEqual([]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("parseResourceDescription refuses anything that is not a model object", () => {
|
|
199
|
+
expect(parseResourceDescription(null)).toBeNull();
|
|
200
|
+
expect(parseResourceDescription({ Identifier: "a" })).toBeNull();
|
|
201
|
+
expect(parseResourceDescription({ Properties: JSON.stringify(["not", "an", "object"]) })).toBeNull();
|
|
202
|
+
});
|
|
203
|
+
});
|