@mapbox/cloudfriend 9.2.0 → 9.3.1

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.
Files changed (139) hide show
  1. package/.claude/settings.local.json +8 -0
  2. package/.idea/cloudfriend.iml +9 -0
  3. package/.idea/codeStyles/Project.xml +7 -0
  4. package/.idea/codeStyles/codeStyleConfig.xml +5 -0
  5. package/.idea/inspectionProfiles/Project_Default.xml +6 -0
  6. package/.idea/misc.xml +6 -0
  7. package/.idea/modules.xml +8 -0
  8. package/.idea/vcs.xml +6 -0
  9. package/.nyc_output/6686e513-03b6-415d-9b8b-0fcde7a4e430.json +1 -0
  10. package/.nyc_output/f58a557c-a1c0-4dcc-83c5-bbca4d01baf9.json +1 -0
  11. package/.nyc_output/processinfo/6686e513-03b6-415d-9b8b-0fcde7a4e430.json +1 -0
  12. package/.nyc_output/processinfo/f58a557c-a1c0-4dcc-83c5-bbca4d01baf9.json +1 -0
  13. package/.nyc_output/processinfo/index.json +1 -1
  14. package/cfniceberg_createtable.yaml +71 -0
  15. package/changelog.md +8 -0
  16. package/coverage/clover.xml +630 -0
  17. package/coverage/coverage-final.json +33 -0
  18. package/coverage/lcov-report/base.css +224 -0
  19. package/coverage/lcov-report/block-navigation.js +87 -0
  20. package/coverage/lcov-report/cloudfriend/bin/build-template.js.html +130 -0
  21. package/coverage/lcov-report/cloudfriend/bin/index.html +131 -0
  22. package/coverage/lcov-report/cloudfriend/bin/validate-template.js.html +142 -0
  23. package/coverage/lcov-report/cloudfriend/index.html +116 -0
  24. package/coverage/lcov-report/cloudfriend/index.js.html +307 -0
  25. package/coverage/lcov-report/cloudfriend/lib/build.js.html +217 -0
  26. package/coverage/lcov-report/cloudfriend/lib/conditions.js.html +430 -0
  27. package/coverage/lcov-report/cloudfriend/lib/index.html +206 -0
  28. package/coverage/lcov-report/cloudfriend/lib/intrinsic.js.html +979 -0
  29. package/coverage/lcov-report/cloudfriend/lib/merge.js.html +478 -0
  30. package/coverage/lcov-report/cloudfriend/lib/pseudo.js.html +370 -0
  31. package/coverage/lcov-report/cloudfriend/lib/rules.js.html +349 -0
  32. package/coverage/lcov-report/cloudfriend/lib/shortcuts/cross-account-role.js.html +244 -0
  33. package/coverage/lcov-report/cloudfriend/lib/shortcuts/event-lambda.js.html +367 -0
  34. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-database.js.html +286 -0
  35. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-iceberg-table.js.html +646 -0
  36. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-json-table.js.html +235 -0
  37. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-orc-table.js.html +226 -0
  38. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-parquet-table.js.html +268 -0
  39. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-presto-view.js.html +358 -0
  40. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-spark-view.js.html +241 -0
  41. package/coverage/lcov-report/cloudfriend/lib/shortcuts/glue-table.js.html +481 -0
  42. package/coverage/lcov-report/cloudfriend/lib/shortcuts/hookshot.js.html +1504 -0
  43. package/coverage/lcov-report/cloudfriend/lib/shortcuts/index.html +431 -0
  44. package/coverage/lcov-report/cloudfriend/lib/shortcuts/index.js.html +157 -0
  45. package/coverage/lcov-report/cloudfriend/lib/shortcuts/kinesis-firehose-base.js.html +418 -0
  46. package/coverage/lcov-report/cloudfriend/lib/shortcuts/lambda.js.html +832 -0
  47. package/coverage/lcov-report/cloudfriend/lib/shortcuts/log-subscription-lambda.js.html +310 -0
  48. package/coverage/lcov-report/cloudfriend/lib/shortcuts/queue-lambda.js.html +364 -0
  49. package/coverage/lcov-report/cloudfriend/lib/shortcuts/queue.js.html +619 -0
  50. package/coverage/lcov-report/cloudfriend/lib/shortcuts/role.js.html +382 -0
  51. package/coverage/lcov-report/cloudfriend/lib/shortcuts/s3-kinesis-firehose.js.html +568 -0
  52. package/coverage/lcov-report/cloudfriend/lib/shortcuts/scheduled-lambda.js.html +490 -0
  53. package/coverage/lcov-report/cloudfriend/lib/shortcuts/service-role.js.html +307 -0
  54. package/coverage/lcov-report/cloudfriend/lib/shortcuts/stream-lambda.js.html +493 -0
  55. package/coverage/lcov-report/cloudfriend/lib/validate.js.html +154 -0
  56. package/coverage/lcov-report/favicon.png +0 -0
  57. package/coverage/lcov-report/index.html +161 -0
  58. package/coverage/lcov-report/prettify.css +1 -0
  59. package/coverage/lcov-report/prettify.js +2 -0
  60. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  61. package/coverage/lcov-report/sorter.js +210 -0
  62. package/coverage/lcov.info +1319 -0
  63. package/jest.config.js +14 -0
  64. package/lib/shortcuts/api.md +37 -0
  65. package/lib/shortcuts/glue-iceberg-table.js +187 -0
  66. package/lib/shortcuts/index.js +1 -0
  67. package/package.json +15 -13
  68. package/readme.md +1 -1
  69. package/requirements.dev.txt +2 -2
  70. package/test/bin.test.js +18 -14
  71. package/test/fixtures/shortcuts/cross-account-role-defaults.json +1 -1
  72. package/test/fixtures/shortcuts/cross-account-role-no-defaults.json +1 -1
  73. package/test/fixtures/shortcuts/event-lambda-defaults.json +1 -1
  74. package/test/fixtures/shortcuts/event-lambda-full.json +1 -1
  75. package/test/fixtures/shortcuts/firehose-defaults.json +1 -1
  76. package/test/fixtures/shortcuts/firehose-with-stream.json +1 -1
  77. package/test/fixtures/shortcuts/glue-database-defaults.json +1 -1
  78. package/test/fixtures/shortcuts/glue-database-no-defaults.json +1 -1
  79. package/test/fixtures/shortcuts/glue-iceberg-table-defaults.json +41 -0
  80. package/test/fixtures/shortcuts/glue-iceberg-table-no-defaults.json +39 -0
  81. package/test/fixtures/shortcuts/glue-iceberg-table-with-all-optimizers.json +101 -0
  82. package/test/fixtures/shortcuts/glue-iceberg-table-with-both-optimizers.json +80 -0
  83. package/test/fixtures/shortcuts/glue-iceberg-table-with-compaction-custom.json +68 -0
  84. package/test/fixtures/shortcuts/glue-iceberg-table-with-compaction-defaults.json +57 -0
  85. package/test/fixtures/shortcuts/glue-iceberg-table-with-optimizer-custom.json +75 -0
  86. package/test/fixtures/shortcuts/glue-iceberg-table-with-optimizer-defaults.json +64 -0
  87. package/test/fixtures/shortcuts/glue-iceberg-table-with-orphan-deletion-custom.json +74 -0
  88. package/test/fixtures/shortcuts/glue-iceberg-table-with-orphan-deletion-defaults.json +62 -0
  89. package/test/fixtures/shortcuts/glue-json-table-defaults.json +1 -1
  90. package/test/fixtures/shortcuts/glue-json-table-no-defaults.json +1 -1
  91. package/test/fixtures/shortcuts/glue-orc-table-defaults.json +1 -1
  92. package/test/fixtures/shortcuts/glue-orc-table-no-defaults.json +1 -1
  93. package/test/fixtures/shortcuts/glue-parquet-table-defaults.json +1 -1
  94. package/test/fixtures/shortcuts/glue-parquet-table-no-defaults.json +1 -1
  95. package/test/fixtures/shortcuts/glue-table-defaults.json +1 -1
  96. package/test/fixtures/shortcuts/glue-table-no-defaults.json +1 -1
  97. package/test/fixtures/shortcuts/glue-view-defaults.json +1 -1
  98. package/test/fixtures/shortcuts/glue-view-no-defaults.json +1 -1
  99. package/test/fixtures/shortcuts/hookshot-github-secret-ref.json +1 -1
  100. package/test/fixtures/shortcuts/hookshot-github-secret-string.json +1 -1
  101. package/test/fixtures/shortcuts/hookshot-github.json +1 -1
  102. package/test/fixtures/shortcuts/hookshot-passthrough-access-log-format.json +1 -1
  103. package/test/fixtures/shortcuts/hookshot-passthrough-alarms.json +1 -1
  104. package/test/fixtures/shortcuts/hookshot-passthrough-enhanced-logging.json +1 -1
  105. package/test/fixtures/shortcuts/hookshot-passthrough-full-blown-logging.json +1 -1
  106. package/test/fixtures/shortcuts/hookshot-passthrough-logging.json +1 -1
  107. package/test/fixtures/shortcuts/hookshot-passthrough.json +1 -1
  108. package/test/fixtures/shortcuts/lambda-defaults.json +1 -1
  109. package/test/fixtures/shortcuts/lambda-docker.json +1 -1
  110. package/test/fixtures/shortcuts/lambda-full.json +1 -1
  111. package/test/fixtures/shortcuts/lambda-provided-role.json +1 -1
  112. package/test/fixtures/shortcuts/lambda-zipfile.json +1 -1
  113. package/test/fixtures/shortcuts/log-subscription-lambda-defaults.json +1 -1
  114. package/test/fixtures/shortcuts/log-subscription-lambda-no-defaults.json +1 -1
  115. package/test/fixtures/shortcuts/queue-defaults.json +1 -1
  116. package/test/fixtures/shortcuts/queue-external-topic-ref.json +1 -1
  117. package/test/fixtures/shortcuts/queue-external-topic.json +1 -1
  118. package/test/fixtures/shortcuts/queue-fifo-queuename.json +1 -1
  119. package/test/fixtures/shortcuts/queue-fifo.json +1 -1
  120. package/test/fixtures/shortcuts/queue-full.json +1 -1
  121. package/test/fixtures/shortcuts/queue-lambda-zero.json +1 -1
  122. package/test/fixtures/shortcuts/queue-lambda.json +1 -1
  123. package/test/fixtures/shortcuts/role-defaults.json +1 -1
  124. package/test/fixtures/shortcuts/role-no-defaults.json +1 -1
  125. package/test/fixtures/shortcuts/scheduled-lambda-defaults.json +1 -1
  126. package/test/fixtures/shortcuts/scheduled-lambda-full.json +1 -1
  127. package/test/fixtures/shortcuts/service-role-defaults.json +1 -1
  128. package/test/fixtures/shortcuts/service-role-no-defaults.json +1 -1
  129. package/test/fixtures/shortcuts/service-role-no-url-suffix.json +1 -1
  130. package/test/fixtures/shortcuts/service-role-url-suffix-with-replacement.json +1 -1
  131. package/test/fixtures/shortcuts/service-role-url-suffix.json +1 -1
  132. package/test/fixtures/shortcuts/stream-lambda-defaults.json +1 -1
  133. package/test/fixtures/shortcuts/stream-lambda-no-defaults.json +1 -1
  134. package/test/index.test.js +383 -235
  135. package/test/shortcuts.test.js +1523 -1483
  136. package/.nyc_output/2b544c0b-2830-4ad0-97cd-8e661710b0bb.json +0 -1
  137. package/.nyc_output/65e58b48-bf1c-412d-8dd4-d7b564b12d76.json +0 -1
  138. package/.nyc_output/processinfo/2b544c0b-2830-4ad0-97cd-8e661710b0bb.json +0 -1
  139. package/.nyc_output/processinfo/65e58b48-bf1c-412d-8dd4-d7b564b12d76.json +0 -1
@@ -1,23 +1,35 @@
1
1
  'use strict';
2
2
 
3
- const test = require('tape');
4
3
  const cloudfriend = require('..');
5
4
  const path = require('path');
6
5
 
7
6
  const expectedTemplate = require('./fixtures/static.json');
8
7
  const fixtures = path.resolve(__dirname, 'fixtures');
9
8
 
10
- test('intrinsic functions', (assert) => {
11
- assert.deepEqual(cloudfriend.base64('secret'), { 'Fn::Base64': 'secret' }, 'base64');
12
- assert.deepEqual(cloudfriend.cidr('ipBlock', 1, 2), { 'Fn::Cidr': ['ipBlock', 1, 2] }, 'cidr');
13
- assert.deepEqual(cloudfriend.findInMap('mapping', 'key', 'value'), { 'Fn::FindInMap': ['mapping', 'key', 'value'] }, 'lookup');
14
- assert.deepEqual(cloudfriend.findInMap('mapping', 'key', 'value', 'hello-world'), { 'Fn::FindInMap': ['mapping', 'key', 'value', { DefaultValue: 'hello-world' }] }, 'lookup with default value');
15
- assert.deepEqual(
16
- cloudfriend.forEach('somethings', 'topic', ['abra', 'cadabra'], 'magic', {
17
- Type: 'AWS::SNS::Topic',
18
- Properties: { TopicName: cloudfriend.ref('topic') }
19
- }),
20
- {
9
+ describe('intrinsic functions', () => {
10
+ test('base64', () => {
11
+ expect(cloudfriend.base64('secret')).toEqual({ 'Fn::Base64': 'secret' });
12
+ });
13
+
14
+ test('cidr', () => {
15
+ expect(cloudfriend.cidr('ipBlock', 1, 2)).toEqual({ 'Fn::Cidr': ['ipBlock', 1, 2] });
16
+ });
17
+
18
+ test('lookup', () => {
19
+ expect(cloudfriend.findInMap('mapping', 'key', 'value')).toEqual({ 'Fn::FindInMap': ['mapping', 'key', 'value'] });
20
+ });
21
+
22
+ test('lookup with default value', () => {
23
+ expect(cloudfriend.findInMap('mapping', 'key', 'value', 'hello-world')).toEqual({ 'Fn::FindInMap': ['mapping', 'key', 'value', { DefaultValue: 'hello-world' }] });
24
+ });
25
+
26
+ test('forEach', () => {
27
+ expect(
28
+ cloudfriend.forEach('somethings', 'topic', ['abra', 'cadabra'], 'magic', {
29
+ Type: 'AWS::SNS::Topic',
30
+ Properties: { TopicName: cloudfriend.ref('topic') }
31
+ })
32
+ ).toEqual({
21
33
  'Fn::ForEach::somethings': [
22
34
  'topic',
23
35
  ['abra', 'cadabra'],
@@ -27,101 +39,205 @@ test('intrinsic functions', (assert) => {
27
39
  Properties: { TopicName: { 'Ref': 'topic' } }
28
40
  }
29
41
  ]
30
- },
31
- 'forEach'
32
- );
33
- assert.deepEqual(cloudfriend.getAtt('obj', 'key'), { 'Fn::GetAtt': ['obj', 'key'] }, 'attr');
34
- assert.deepEqual(cloudfriend.getAzs(), { 'Fn::GetAZs': '' }, 'azs (no value specified)');
35
- assert.deepEqual(cloudfriend.getAzs('us-east-1'), { 'Fn::GetAZs': 'us-east-1' }, 'azs (value specified)');
36
- assert.deepEqual(cloudfriend.join(['abra', 'cadabra']), { 'Fn::Join': ['', ['abra', 'cadabra']] }, 'join (no delimeter specified)');
37
- assert.deepEqual(cloudfriend.join('-', ['abra', 'cadabra']), { 'Fn::Join': ['-', ['abra', 'cadabra']] }, 'join (delimeter specified)');
38
- assert.deepEqual(cloudfriend.select(1, ['abra', 'cadabra']), { 'Fn::Select': ['1', ['abra', 'cadabra']] }, '');
39
- assert.deepEqual(cloudfriend.ref('something'), { Ref: 'something' }, 'ref');
40
- assert.deepEqual(cloudfriend.split(',', 'a,b,c,d'), { 'Fn::Split': [',', 'a,b,c,d'] }, 'split');
41
- assert.deepEqual(cloudfriend.split(',', cloudfriend.ref('Id')), { 'Fn::Split': [',', { Ref: 'Id' }] }, 'split');
42
- assert.deepEqual(cloudfriend.userData(['#!/usr/bin/env bash', 'set -e']), { 'Fn::Base64': { 'Fn::Join': ['\n', ['#!/usr/bin/env bash', 'set -e']] } }, 'userData');
43
- assert.deepEqual(cloudfriend.sub('my ${thing}'), { 'Fn::Sub': 'my ${thing}' }, 'sub without variables');
44
- assert.deepEqual(cloudfriend.sub('my ${thing}', { thing: 'stuff' }), { 'Fn::Sub': ['my ${thing}', { thing: 'stuff' }] }, 'sub with variables');
45
- assert.deepEqual(cloudfriend.importValue('id'), { 'Fn::ImportValue': 'id' }, 'import value with string');
46
- assert.deepEqual(cloudfriend.importValue(cloudfriend.ref('Id')), { 'Fn::ImportValue': cloudfriend.ref('Id') }, 'import value with string');
47
- assert.deepEqual(cloudfriend.arn('s3', 'my-bucket/*'), { 'Fn::Sub': ['arn:${AWS::Partition}:${service}:::${suffix}', { service: 's3', suffix: 'my-bucket/*' }] }, 's3 arn');
48
- assert.deepEqual(cloudfriend.arn('cloudformation', 'stack/my-stack/*'), { 'Fn::Sub': ['arn:${AWS::Partition}:${service}:${AWS::Region}:${AWS::AccountId}:${suffix}', { service: 'cloudformation', suffix: 'stack/my-stack/*' }] }, 'non-s3 arn');
49
- assert.deepEqual(cloudfriend.transform('name', { 'a': 'b', 'c': 'd' }), { 'Fn::Transform': { Name: 'name', Parameters: { 'a': 'b', 'c': 'd' } } }, 'cidr');
50
- assert.end();
42
+ });
43
+ });
44
+
45
+ test('attr', () => {
46
+ expect(cloudfriend.getAtt('obj', 'key')).toEqual({ 'Fn::GetAtt': ['obj', 'key'] });
47
+ });
48
+
49
+ test('azs (no value specified)', () => {
50
+ expect(cloudfriend.getAzs()).toEqual({ 'Fn::GetAZs': '' });
51
+ });
52
+
53
+ test('azs (value specified)', () => {
54
+ expect(cloudfriend.getAzs('us-east-1')).toEqual({ 'Fn::GetAZs': 'us-east-1' });
55
+ });
56
+
57
+ test('join (no delimeter specified)', () => {
58
+ expect(cloudfriend.join(['abra', 'cadabra'])).toEqual({ 'Fn::Join': ['', ['abra', 'cadabra']] });
59
+ });
60
+
61
+ test('join (delimeter specified)', () => {
62
+ expect(cloudfriend.join('-', ['abra', 'cadabra'])).toEqual({ 'Fn::Join': ['-', ['abra', 'cadabra']] });
63
+ });
64
+
65
+ test('select', () => {
66
+ expect(cloudfriend.select(1, ['abra', 'cadabra'])).toEqual({ 'Fn::Select': ['1', ['abra', 'cadabra']] });
67
+ });
68
+
69
+ test('ref', () => {
70
+ expect(cloudfriend.ref('something')).toEqual({ Ref: 'something' });
71
+ });
72
+
73
+ test('split', () => {
74
+ expect(cloudfriend.split(',', 'a,b,c,d')).toEqual({ 'Fn::Split': [',', 'a,b,c,d'] });
75
+ });
76
+
77
+ test('split with ref', () => {
78
+ expect(cloudfriend.split(',', cloudfriend.ref('Id'))).toEqual({ 'Fn::Split': [',', { Ref: 'Id' }] });
79
+ });
80
+
81
+ test('userData', () => {
82
+ expect(cloudfriend.userData(['#!/usr/bin/env bash', 'set -e'])).toEqual({ 'Fn::Base64': { 'Fn::Join': ['\n', ['#!/usr/bin/env bash', 'set -e']] } });
83
+ });
84
+
85
+ test('sub without variables', () => {
86
+ expect(cloudfriend.sub('my ${thing}')).toEqual({ 'Fn::Sub': 'my ${thing}' });
87
+ });
88
+
89
+ test('sub with variables', () => {
90
+ expect(cloudfriend.sub('my ${thing}', { thing: 'stuff' })).toEqual({ 'Fn::Sub': ['my ${thing}', { thing: 'stuff' }] });
91
+ });
92
+
93
+ test('import value with string', () => {
94
+ expect(cloudfriend.importValue('id')).toEqual({ 'Fn::ImportValue': 'id' });
95
+ });
96
+
97
+ test('import value with ref', () => {
98
+ expect(cloudfriend.importValue(cloudfriend.ref('Id'))).toEqual({ 'Fn::ImportValue': cloudfriend.ref('Id') });
99
+ });
100
+
101
+ test('s3 arn', () => {
102
+ expect(cloudfriend.arn('s3', 'my-bucket/*')).toEqual({ 'Fn::Sub': ['arn:${AWS::Partition}:${service}:::${suffix}', { service: 's3', suffix: 'my-bucket/*' }] });
103
+ });
104
+
105
+ test('non-s3 arn', () => {
106
+ expect(cloudfriend.arn('cloudformation', 'stack/my-stack/*')).toEqual({ 'Fn::Sub': ['arn:${AWS::Partition}:${service}:${AWS::Region}:${AWS::AccountId}:${suffix}', { service: 'cloudformation', suffix: 'stack/my-stack/*' }] });
107
+ });
108
+
109
+ test('transform', () => {
110
+ expect(cloudfriend.transform('name', { 'a': 'b', 'c': 'd' })).toEqual({ 'Fn::Transform': { Name: 'name', Parameters: { 'a': 'b', 'c': 'd' } } });
111
+ });
51
112
  });
52
113
 
53
- test('conditions', (assert) => {
54
- assert.deepEqual(cloudfriend.and(['a', 'b']), { 'Fn::And': ['a', 'b'] }, 'and');
55
- assert.deepEqual(cloudfriend.equals('a', 'b'), { 'Fn::Equals': ['a', 'b'] }, 'equal');
56
- assert.deepEqual(cloudfriend.if('condition', 'a', 'b'), { 'Fn::If': ['condition', 'a', 'b'] }, 'if');
57
- assert.deepEqual(cloudfriend.not('condition'), { 'Fn::Not': ['condition'] }, 'not');
58
- assert.deepEqual(cloudfriend.or(['a', 'b']), { 'Fn::Or': ['a', 'b'] }, 'or');
59
- assert.deepEqual(cloudfriend.notEquals('a', 'b'), { 'Fn::Not': [{ 'Fn::Equals': ['a', 'b'] }] }, 'notEqual');
60
- assert.end();
114
+ describe('conditions', () => {
115
+ test('and', () => {
116
+ expect(cloudfriend.and(['a', 'b'])).toEqual({ 'Fn::And': ['a', 'b'] });
117
+ });
118
+
119
+ test('equal', () => {
120
+ expect(cloudfriend.equals('a', 'b')).toEqual({ 'Fn::Equals': ['a', 'b'] });
121
+ });
122
+
123
+ test('if', () => {
124
+ expect(cloudfriend.if('condition', 'a', 'b')).toEqual({ 'Fn::If': ['condition', 'a', 'b'] });
125
+ });
126
+
127
+ test('not', () => {
128
+ expect(cloudfriend.not('condition')).toEqual({ 'Fn::Not': ['condition'] });
129
+ });
130
+
131
+ test('or', () => {
132
+ expect(cloudfriend.or(['a', 'b'])).toEqual({ 'Fn::Or': ['a', 'b'] });
133
+ });
134
+
135
+ test('notEqual', () => {
136
+ expect(cloudfriend.notEquals('a', 'b')).toEqual({ 'Fn::Not': [{ 'Fn::Equals': ['a', 'b'] }] });
137
+ });
61
138
  });
62
139
 
63
- test('rules', (assert) => {
64
- assert.deepEqual(cloudfriend.contains(['a', 'b'], 'a'), { 'Fn::Contains': [['a', 'b'], 'a'] });
65
- assert.deepEqual(cloudfriend.eachMemberEquals(['a', 'a'], 'a'), { 'Fn::EachMemberEquals': [['a', 'a'], 'a'] });
66
- assert.deepEqual(cloudfriend.eachMemberIn(['a', 'b'], ['a', 'b', 'c']), { 'Fn::EachMemberIn': [['a', 'b'], ['a', 'b', 'c']] });
67
- assert.deepEqual(cloudfriend.refAll('a'), { 'Fn::RefAll': 'a' });
68
- assert.deepEqual(cloudfriend.valueOf('a', 'b'), { 'Fn::ValueOf': ['a', 'b'] });
69
- assert.deepEqual(cloudfriend.valueOfAll('a', 'b'), { 'Fn::ValueOfAll': ['a', 'b'] });
70
- assert.end();
140
+ describe('rules', () => {
141
+ test('contains', () => {
142
+ expect(cloudfriend.contains(['a', 'b'], 'a')).toEqual({ 'Fn::Contains': [['a', 'b'], 'a'] });
143
+ });
144
+
145
+ test('eachMemberEquals', () => {
146
+ expect(cloudfriend.eachMemberEquals(['a', 'a'], 'a')).toEqual({ 'Fn::EachMemberEquals': [['a', 'a'], 'a'] });
147
+ });
148
+
149
+ test('eachMemberIn', () => {
150
+ expect(cloudfriend.eachMemberIn(['a', 'b'], ['a', 'b', 'c'])).toEqual({ 'Fn::EachMemberIn': [['a', 'b'], ['a', 'b', 'c']] });
151
+ });
152
+
153
+ test('refAll', () => {
154
+ expect(cloudfriend.refAll('a')).toEqual({ 'Fn::RefAll': 'a' });
155
+ });
156
+
157
+ test('valueOf', () => {
158
+ expect(cloudfriend.valueOf('a', 'b')).toEqual({ 'Fn::ValueOf': ['a', 'b'] });
159
+ });
160
+
161
+ test('valueOfAll', () => {
162
+ expect(cloudfriend.valueOfAll('a', 'b')).toEqual({ 'Fn::ValueOfAll': ['a', 'b'] });
163
+ });
71
164
  });
72
165
 
73
- test('pseudo', (assert) => {
74
- assert.deepEqual(cloudfriend.accountId, { Ref: 'AWS::AccountId' }, 'account');
75
- assert.deepEqual(cloudfriend.notificationArns, { Ref: 'AWS::NotificationARNs' }, 'notificationArns');
76
- assert.deepEqual(cloudfriend.noValue, { Ref: 'AWS::NoValue' }, 'noValue');
77
- assert.deepEqual(cloudfriend.region, { Ref: 'AWS::Region' }, 'region');
78
- assert.deepEqual(cloudfriend.stackId, { Ref: 'AWS::StackId' }, 'stackId');
79
- assert.deepEqual(cloudfriend.stackName, { Ref: 'AWS::StackName' }, 'stack');
80
- assert.deepEqual(cloudfriend.partition, { Ref: 'AWS::Partition' }, 'stack');
81
- assert.deepEqual(cloudfriend.urlSuffix, { Ref: 'AWS::URLSuffix' }, 'stack');
82
- assert.end();
166
+ describe('pseudo', () => {
167
+ test('account', () => {
168
+ expect(cloudfriend.accountId).toEqual({ Ref: 'AWS::AccountId' });
169
+ });
170
+
171
+ test('notificationArns', () => {
172
+ expect(cloudfriend.notificationArns).toEqual({ Ref: 'AWS::NotificationARNs' });
173
+ });
174
+
175
+ test('noValue', () => {
176
+ expect(cloudfriend.noValue).toEqual({ Ref: 'AWS::NoValue' });
177
+ });
178
+
179
+ test('region', () => {
180
+ expect(cloudfriend.region).toEqual({ Ref: 'AWS::Region' });
181
+ });
182
+
183
+ test('stackId', () => {
184
+ expect(cloudfriend.stackId).toEqual({ Ref: 'AWS::StackId' });
185
+ });
186
+
187
+ test('stackName', () => {
188
+ expect(cloudfriend.stackName).toEqual({ Ref: 'AWS::StackName' });
189
+ });
190
+
191
+ test('partition', () => {
192
+ expect(cloudfriend.partition).toEqual({ Ref: 'AWS::Partition' });
193
+ });
194
+
195
+ test('urlSuffix', () => {
196
+ expect(cloudfriend.urlSuffix).toEqual({ Ref: 'AWS::URLSuffix' });
197
+ });
83
198
  });
84
199
 
85
- test('build', (assert) => {
86
- assert.plan(8);
87
-
88
- cloudfriend.build(path.join(fixtures, 'static.json'))
89
- .then((template) => {
90
- assert.deepEqual(template, expectedTemplate, 'static.json');
91
- return cloudfriend.build(path.join(fixtures, 'static.js'));
92
- })
93
- .then((template) => {
94
- assert.deepEqual(template, expectedTemplate, 'static.js');
95
- return cloudfriend.build(path.join(fixtures, 'sync.js'));
96
- })
97
- .then((template) => {
98
- assert.deepEqual(template, expectedTemplate, 'sync.js');
99
- return cloudfriend.build(path.join(fixtures, 'async.js'));
100
- })
101
- .then((template) => {
102
- assert.deepEqual(template, expectedTemplate, 'async.js (success)');
103
- return cloudfriend.build(path.join(fixtures, 'async-error.js'))
104
- .catch((err) => {
105
- assert.ok(err, 'async.js (error)');
106
- });
107
- })
108
- .then(() => {
109
- return cloudfriend.build(path.join(fixtures, 'sync-args.js'), { some: 'options' });
110
- })
111
- .then((template) => {
112
- assert.deepEqual(template, { some: 'options' }, 'passes args (sync)');
113
- return cloudfriend.build(path.join(fixtures, 'async-args.js'), { some: 'options' });
114
- })
115
- .then((template) => {
116
- assert.deepEqual(template, { some: 'options' }, 'passes args (async)');
117
- return cloudfriend.build(path.join(fixtures, 'malformed.json'))
118
- .catch((err) => {
119
- assert.ok(err, 'malformed JSON (error)');
120
- });
121
- });
200
+ describe('build', () => {
201
+ test('static.json', async () => {
202
+ const template = await cloudfriend.build(path.join(fixtures, 'static.json'));
203
+ expect(template).toEqual(expectedTemplate);
204
+ });
205
+
206
+ test('static.js', async () => {
207
+ const template = await cloudfriend.build(path.join(fixtures, 'static.js'));
208
+ expect(template).toEqual(expectedTemplate);
209
+ });
210
+
211
+ test('sync.js', async () => {
212
+ const template = await cloudfriend.build(path.join(fixtures, 'sync.js'));
213
+ expect(template).toEqual(expectedTemplate);
214
+ });
215
+
216
+ test('async.js (success)', async () => {
217
+ const template = await cloudfriend.build(path.join(fixtures, 'async.js'));
218
+ expect(template).toEqual(expectedTemplate);
219
+ });
220
+
221
+ test('async.js (error)', async () => {
222
+ await expect(cloudfriend.build(path.join(fixtures, 'async-error.js'))).rejects.toBeTruthy();
223
+ });
224
+
225
+ test('passes args (sync)', async () => {
226
+ const template = await cloudfriend.build(path.join(fixtures, 'sync-args.js'), { some: 'options' });
227
+ expect(template).toEqual({ some: 'options' });
228
+ });
229
+
230
+ test('passes args (async)', async () => {
231
+ const template = await cloudfriend.build(path.join(fixtures, 'async-args.js'), { some: 'options' });
232
+ expect(template).toEqual({ some: 'options' });
233
+ });
234
+
235
+ test('malformed JSON (error)', async () => {
236
+ await expect(cloudfriend.build(path.join(fixtures, 'malformed.json'))).rejects.toBeTruthy();
237
+ });
122
238
  });
123
239
 
124
- test('merge', (assert) => {
240
+ describe('merge', () => {
125
241
  const a = {
126
242
  Metadata: { Instances: { Description: 'Information about the instances' } },
127
243
  Parameters: { InstanceCount: { Type: 'Number' } },
@@ -133,62 +249,65 @@ test('merge', (assert) => {
133
249
  Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') } }
134
250
  };
135
251
 
136
- let b = {
137
- Metadata: { Databases: { Description: 'Information about the databases' } },
138
- Parameters: { DatabasePrefix: { Type: 'String' } },
139
- Rules: { YouMustHaveBacon: { Assertions: [{ Assert: cloudfriend.and([cloudfriend.not('WouldYouLikeBaconWithThat'), 'TooMuch']) }] } },
140
- Mappings: { Prefix: { eggs: { Name: 'bananas' } } },
141
- Conditions: { TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon') },
142
- Transform: ['TransformA'],
143
- Resources: { Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } } },
144
- Outputs: { GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') } }
145
- };
252
+ test('merge without overlap', () => {
253
+ const b = {
254
+ Metadata: { Databases: { Description: 'Information about the databases' } },
255
+ Parameters: { DatabasePrefix: { Type: 'String' } },
256
+ Rules: { YouMustHaveBacon: { Assertions: [{ Assert: cloudfriend.and([cloudfriend.not('WouldYouLikeBaconWithThat'), 'TooMuch']) }] } },
257
+ Mappings: { Prefix: { eggs: { Name: 'bananas' } } },
258
+ Conditions: { TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon') },
259
+ Transform: ['TransformA'],
260
+ Resources: { Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } } },
261
+ Outputs: { GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') } }
262
+ };
146
263
 
147
- const c = {
148
- Parameters: { NoConsequence: { Type: 'String' } }
149
- };
264
+ const c = {
265
+ Parameters: { NoConsequence: { Type: 'String' } }
266
+ };
267
+
268
+ expect(cloudfriend.merge(a, b, c)).toEqual({
269
+ AWSTemplateFormatVersion: '2010-09-09',
270
+ Metadata: {
271
+ Instances: { Description: 'Information about the instances' },
272
+ Databases: { Description: 'Information about the databases' }
273
+ },
274
+ Parameters: {
275
+ InstanceCount: { Type: 'Number' },
276
+ DatabasePrefix: { Type: 'String' },
277
+ NoConsequence: { Type: 'String' }
278
+ },
279
+ Rules: {
280
+ WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat') }] },
281
+ YouMustHaveBacon: { Assertions: [{ Assert: cloudfriend.and([cloudfriend.not('WouldYouLikeBaconWithThat'), 'TooMuch']) }] }
282
+ },
283
+ Mappings: {
284
+ Region: { 'us-east-1': { AMI: 'ami-123456' } },
285
+ Prefix: { eggs: { Name: 'bananas' } }
286
+ },
287
+ Conditions: {
288
+ WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999),
289
+ TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon')
290
+ },
291
+ Transform: ['TransformB', 'TransformA'],
292
+ Resources: {
293
+ Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } },
294
+ Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } }
295
+ },
296
+ Outputs: {
297
+ Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') },
298
+ GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') }
299
+ }
300
+ });
301
+ });
150
302
 
151
- assert.deepEqual(cloudfriend.merge(a, b, c), {
152
- AWSTemplateFormatVersion: '2010-09-09',
153
- Metadata: {
154
- Instances: { Description: 'Information about the instances' },
155
- Databases: { Description: 'Information about the databases' }
156
- },
157
- Parameters: {
158
- InstanceCount: { Type: 'Number' },
159
- DatabasePrefix: { Type: 'String' },
160
- NoConsequence: { Type: 'String' }
161
- },
162
- Rules: {
163
- WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat') }] },
164
- YouMustHaveBacon: { Assertions: [{ Assert: cloudfriend.and([cloudfriend.not('WouldYouLikeBaconWithThat'), 'TooMuch']) }] }
165
- },
166
- Mappings: {
167
- Region: { 'us-east-1': { AMI: 'ami-123456' } },
168
- Prefix: { eggs: { Name: 'bananas' } }
169
- },
170
- Conditions: {
171
- WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999),
172
- TooMuch: cloudfriend.equals(cloudfriend.ref('DatabasePrefix'), 'bacon')
173
- },
174
- Transform: ['TransformB', 'TransformA'],
175
- Resources: {
176
- Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } },
177
- Database: { Type: 'AWS::DynamoDB::Table', Properties: { Name: cloudfriend.findInMap('Prefix', cloudfriend.ref('DatabasePrefix'), 'Name') } }
178
- },
179
- Outputs: {
180
- Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') },
181
- GoSomewhereElse: { Condition: 'TooMuch', Value: cloudfriend.ref('Database') }
182
- }
183
- }, 'merge without overlap');
184
-
185
- assert.deepEqual(
186
- cloudfriend.merge(
187
- { Transform: 'foo' },
188
- { Transform: ['baz', 'bar'] },
189
- { Parameters: { InstanceCount: { Type: 'Number' } } }
190
- ),
191
- {
303
+ test('merge with string, array, and empty Transforms, ignoring duplicates', () => {
304
+ expect(
305
+ cloudfriend.merge(
306
+ { Transform: 'foo' },
307
+ { Transform: ['baz', 'bar'] },
308
+ { Parameters: { InstanceCount: { Type: 'Number' } } }
309
+ )
310
+ ).toEqual({
192
311
  AWSTemplateFormatVersion: '2010-09-09',
193
312
  Metadata: {},
194
313
  Parameters: { InstanceCount: { Type: 'Number' } },
@@ -198,88 +317,117 @@ test('merge', (assert) => {
198
317
  Transform: ['foo', 'baz', 'bar'],
199
318
  Resources: {},
200
319
  Outputs: {}
201
- },
202
- 'merge with string, array, and empty Transforms, ignoring duplicates'
203
- );
204
-
205
- assert.throws(() => {
206
- cloudfriend.merge({ Transform: ['foo', 'bar'] }, { Transform: ['baz', 'bar'] });
207
- }, /Transform macro used more than once: bar/);
208
-
209
- assert.throws(() => {
210
- b = { Metadata: { Instances: { Description: 'Information about the instances different' } } };
211
- cloudfriend.merge(a, b);
212
- }, /Metadata name used more than once: Instances/, 'throws on .Metadata overlap');
213
-
214
- assert.doesNotThrow(() => {
215
- b = { Metadata: { Instances: { Description: 'Information about the instances' } } };
216
- cloudfriend.merge(a, b);
217
- }, 'allows identical .Metadata overlap');
218
-
219
- assert.throws(() => {
220
- b = { Parameters: { InstanceCount: { Type: 'Number', Description: 'Different' } } };
221
- cloudfriend.merge(a, b);
222
- }, /Parameters name used more than once: InstanceCount/, 'throws on .Parameters overlap');
223
-
224
- assert.doesNotThrow(() => {
225
- b = { Parameters: { InstanceCount: { Type: 'Number' } } };
226
- cloudfriend.merge(a, b);
227
- }, 'allows identical .Parameters overlap');
228
-
229
- assert.throws(() => {
230
- b = { Rules: { WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat'), AssertDescription: 'Different' }] } } };
231
- cloudfriend.merge(a, b);
232
- }, /Rules name used more than once: WeAreOutOfBacon/, 'throws in .Rules overlap');
233
-
234
- assert.doesNotThrow(() => {
235
- b = { Rules: { WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat') }] } } };
236
- cloudfriend.merge(a, b);
237
- }, 'allows identical .Rules overlap');
238
-
239
- assert.throws(() => {
240
- b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' }, 'us-east-4': { AMI: 'ami-123456' } } } };
241
- cloudfriend.merge(a, b);
242
- }, /Mappings name used more than once: Region/, 'throws on .Mappings overlap');
243
-
244
- assert.doesNotThrow(() => {
245
- b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' } } } };
246
- cloudfriend.merge(a, b);
247
- }, 'allows identical .Mappings overlap');
248
-
249
- assert.throws(() => {
250
- b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 998) } };
251
- cloudfriend.merge(a, b);
252
- }, /Conditions name used more than once: WouldYouLikeBaconWithThat/, 'throws on .Conditions overlap');
253
-
254
- assert.doesNotThrow(() => {
255
- b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999) } };
256
- cloudfriend.merge(a, b);
257
- }, 'allows identical .Conditions overlap');
258
-
259
- assert.throws(() => {
260
- b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMIz') } } } };
261
- cloudfriend.merge(a, b);
262
- }, /Resources name used more than once: Instance/, 'throws on .Resources overlap');
263
-
264
- assert.doesNotThrow(() => {
265
- b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } } } };
266
- cloudfriend.merge(a, b);
267
- }, 'allows identical .Resources overlap');
268
-
269
- assert.throws(() => {
270
- b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instancez') } } };
271
- cloudfriend.merge(a, b);
272
- }, /Outputs name used more than once: Breakfast/, 'throws on .Outputs overlap');
273
-
274
- assert.doesNotThrow(() => {
275
- b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') } } };
276
- cloudfriend.merge(a, b);
277
- }, 'allows identical .Outputs overlap');
278
-
279
- assert.doesNotThrow(() => {
280
- b = { Mappings: { Instance: { 'us-east-1': { AMI: 'ami-123456' } } } };
281
- cloudfriend.merge(a, b);
282
- }, 'does not throw on cross-property name overlap');
283
-
284
- assert.end();
320
+ });
321
+ });
322
+
323
+ test('throws on duplicate Transform macro', () => {
324
+ expect(() => {
325
+ cloudfriend.merge({ Transform: ['foo', 'bar'] }, { Transform: ['baz', 'bar'] });
326
+ }).toThrow(/Transform macro used more than once: bar/);
327
+ });
328
+
329
+ test('throws on .Metadata overlap', () => {
330
+ const b = { Metadata: { Instances: { Description: 'Information about the instances different' } } };
331
+ expect(() => {
332
+ cloudfriend.merge(a, b);
333
+ }).toThrow(/Metadata name used more than once: Instances/);
334
+ });
335
+
336
+ test('allows identical .Metadata overlap', () => {
337
+ const b = { Metadata: { Instances: { Description: 'Information about the instances' } } };
338
+ expect(() => {
339
+ cloudfriend.merge(a, b);
340
+ }).not.toThrow();
341
+ });
342
+
343
+ test('throws on .Parameters overlap', () => {
344
+ const b = { Parameters: { InstanceCount: { Type: 'Number', Description: 'Different' } } };
345
+ expect(() => {
346
+ cloudfriend.merge(a, b);
347
+ }).toThrow(/Parameters name used more than once: InstanceCount/);
348
+ });
349
+
350
+ test('allows identical .Parameters overlap', () => {
351
+ const b = { Parameters: { InstanceCount: { Type: 'Number' } } };
352
+ expect(() => {
353
+ cloudfriend.merge(a, b);
354
+ }).not.toThrow();
355
+ });
356
+
357
+ test('throws in .Rules overlap', () => {
358
+ const b = { Rules: { WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat'), AssertDescription: 'Different' }] } } };
359
+ expect(() => {
360
+ cloudfriend.merge(a, b);
361
+ }).toThrow(/Rules name used more than once: WeAreOutOfBacon/);
362
+ });
363
+
364
+ test('allows identical .Rules overlap', () => {
365
+ const b = { Rules: { WeAreOutOfBacon: { Assertions: [{ Assert: cloudfriend.not('WouldYouLikeBaconWithThat') }] } } };
366
+ expect(() => {
367
+ cloudfriend.merge(a, b);
368
+ }).not.toThrow();
369
+ });
370
+
371
+ test('throws on .Mappings overlap', () => {
372
+ const b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' }, 'us-east-4': { AMI: 'ami-123456' } } } };
373
+ expect(() => {
374
+ cloudfriend.merge(a, b);
375
+ }).toThrow(/Mappings name used more than once: Region/);
376
+ });
377
+
378
+ test('allows identical .Mappings overlap', () => {
379
+ const b = { Mappings: { Region: { 'us-east-1': { AMI: 'ami-123456' } } } };
380
+ expect(() => {
381
+ cloudfriend.merge(a, b);
382
+ }).not.toThrow();
383
+ });
384
+
385
+ test('throws on .Conditions overlap', () => {
386
+ const b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 998) } };
387
+ expect(() => {
388
+ cloudfriend.merge(a, b);
389
+ }).toThrow(/Conditions name used more than once: WouldYouLikeBaconWithThat/);
390
+ });
391
+
392
+ test('allows identical .Conditions overlap', () => {
393
+ const b = { Conditions: { WouldYouLikeBaconWithThat: cloudfriend.equals(cloudfriend.ref('InstanceCount'), 999) } };
394
+ expect(() => {
395
+ cloudfriend.merge(a, b);
396
+ }).not.toThrow();
397
+ });
398
+
399
+ test('throws on .Resources overlap', () => {
400
+ const b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMIz') } } } };
401
+ expect(() => {
402
+ cloudfriend.merge(a, b);
403
+ }).toThrow(/Resources name used more than once: Instance/);
404
+ });
405
+
406
+ test('allows identical .Resources overlap', () => {
407
+ const b = { Resources: { Instance: { Type: 'AWS::EC2::Instance', Properties: { ImageId: cloudfriend.findInMap('Region', cloudfriend.region, 'AMI') } } } };
408
+ expect(() => {
409
+ cloudfriend.merge(a, b);
410
+ }).not.toThrow();
411
+ });
412
+
413
+ test('throws on .Outputs overlap', () => {
414
+ const b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instancez') } } };
415
+ expect(() => {
416
+ cloudfriend.merge(a, b);
417
+ }).toThrow(/Outputs name used more than once: Breakfast/);
418
+ });
419
+
420
+ test('allows identical .Outputs overlap', () => {
421
+ const b = { Outputs: { Breakfast: { Condition: 'WouldYouLikeBaconWithThat', Value: cloudfriend.ref('Instance') } } };
422
+ expect(() => {
423
+ cloudfriend.merge(a, b);
424
+ }).not.toThrow();
425
+ });
426
+
427
+ test('does not throw on cross-property name overlap', () => {
428
+ const b = { Mappings: { Instance: { 'us-east-1': { AMI: 'ami-123456' } } } };
429
+ expect(() => {
430
+ cloudfriend.merge(a, b);
431
+ }).not.toThrow();
432
+ });
285
433
  });