diff --git a/README.md b/README.md index b3fa7ddc..baad030f 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# replace this \ No newline at end of file +# Webformulieren submission storage + +Deze applicatie is verantwoordelijk voor de opslag van formulierinzendingen, voor afnemers (Mijn Nijmegen) die deze moeten kunnen tonen aan de indiener. De applicatie is gesubscribed op het SNS-topic dat in [webformulieren](https://github.com/gemeentenijmegen/webformulieren) bestaat, en verwerkt inzendingen bij binnenkomst. + +Het is nog niet mogelijk ingezonden formulieren op te halen. diff --git a/src/ApiStack.ts b/src/ApiStack.ts index f234e3d8..0c32e367 100644 --- a/src/ApiStack.ts +++ b/src/ApiStack.ts @@ -1,33 +1,70 @@ -import { Stack } from 'aws-cdk-lib'; +import { Stack, StackProps } from 'aws-cdk-lib'; +import { AnyPrincipal, Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; +import { ITopic, Topic } from 'aws-cdk-lib/aws-sns'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { Construct } from 'constructs'; +import { Configurable } from './Configuration'; import { Statics } from './statics'; import { SubmissionSnsEventHandler } from './SubmissionSnsEventHandler'; +interface ApiStackProps extends StackProps, Configurable {}; /** * Contains all API-related resources. */ export class ApiStack extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); - - // const api = new RestApi(this, 'gateway'); - // api.root.addMethod('ANY', new MockIntegration({ - // integrationResponses: [ - // { statusCode: '200' }, - // ], - // passthroughBehavior: PassthroughBehavior.NEVER, - // requestTemplates: { - // 'application/json': '{ "statusCode": 200 }', - // }, - // }), { - // methodResponses: [ - // { statusCode: '200' }, - // ], - // }); + constructor(scope: Construct, id: string, props: ApiStackProps) { + super(scope, id, props); + const internalTopic = new SNSTopic(this, 'submissions', { publishingAccountIds: props.configuration.allowedAccountIdsToPublishToSNS }); new SubmissionSnsEventHandler(this, 'submissionhandler', { - topicArn: StringParameter.valueForStringParameter(this, Statics.ssmSubmissionTopicArn), + topicArns: [internalTopic.topic.topicArn, StringParameter.valueForStringParameter(this, Statics.ssmSubmissionTopicArn)], + }); + } +} + +interface SNSTopicProps extends StackProps { + /** + * Allow access for different AWS accounts to publish to this topic + */ + publishingAccountIds?: string[]; +} +class SNSTopic extends Construct { + topic: ITopic; + constructor(scope: Construct, id: string, props: SNSTopicProps) { + super(scope, id); + + this.topic = new Topic(this, 'submissions', { + displayName: 'submissions', }); + + this.allowCrossAccountAccess(props.publishingAccountIds); + } + + /** + * Allow cross account access to this topic + * + * This allows lambda's with the execution role 'storesubmissions-lambda-role' + * in the accounts in `allowedAccountIds` access to publish to this topic. + * + * @param allowedAccountIds array of account IDs + */ + allowCrossAccountAccess(allowedAccountIds?: string[]): void { + if (!allowedAccountIds || allowedAccountIds.length == 0) { return; } + const crossAccountPrincipalArns = allowedAccountIds.map( + (accountId) => `arn:aws:iam::${accountId}:role/storesubmissions-lambda-role`, + ); + this.topic.addToResourcePolicy(new PolicyStatement({ + effect: Effect.ALLOW, + actions: [ + 'SNS:Publish', + ], + resources: [this.topic.topicArn], + principals: [new AnyPrincipal()], + conditions: { + ArnLike: { + 'aws:PrincipalArn': crossAccountPrincipalArns, + }, + }, + })); } } diff --git a/src/ApiStage.ts b/src/ApiStage.ts index d0708c64..3cf453eb 100644 --- a/src/ApiStage.ts +++ b/src/ApiStage.ts @@ -2,7 +2,7 @@ import { PermissionsBoundaryAspect } from '@gemeentenijmegen/aws-constructs'; import { Aspects, Stage, StageProps, Tags } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { ApiStack } from './ApiStack'; -import { Configurable, Configuration } from './Configuration'; +import { Configurable } from './Configuration'; import { Statics } from './statics'; import { StorageStack } from './StorageStack'; @@ -13,8 +13,6 @@ interface ApiStageProps extends StageProps, Configurable { } */ export class ApiStage extends Stage { - readonly configuration: Configuration; - constructor(scope: Construct, id: string, props: ApiStageProps) { super(scope, id, props); @@ -22,11 +20,11 @@ export class ApiStage extends Stage { Tags.of(this).add('Project', Statics.projectName); Aspects.of(this).add(new PermissionsBoundaryAspect()); - this.configuration = props.configuration; + const configuration = props.configuration; - const storageStack = new StorageStack(this, 'storage'); + const storageStack = new StorageStack(this, 'storage', { configuration }); - const apiStack = new ApiStack(this, 'api'); + const apiStack = new ApiStack(this, 'api', { configuration } ); apiStack.addDependency(storageStack); } } diff --git a/src/Configuration.ts b/src/Configuration.ts index 191651d5..2593be66 100644 --- a/src/Configuration.ts +++ b/src/Configuration.ts @@ -26,6 +26,13 @@ export interface Configuration { * includePipelineValidationChcks */ readonly includePipelineValidationChecks: boolean; + + /** + * Allow this project's SNS topic to be published to + * by other accounts. This allows access to lambda + * execution roles named 'storesubmissions-lambda-role'. + */ + readonly allowedAccountIdsToPublishToSNS?: string[]; } export function getConfiguration(branchName: string): Configuration { @@ -45,6 +52,7 @@ const configurations: { [name: string] : Configuration } = { deployFromEnvironment: Statics.gnBuildEnvironment, deployToEnvironment: Statics.appDevEnvironment, includePipelineValidationChecks: false, + allowedAccountIdsToPublishToSNS: [Statics.acceptanceWebformulierenAccountId], }, production: { branchName: 'main', diff --git a/src/StorageStack.ts b/src/StorageStack.ts index b949d4c7..669fb12b 100644 --- a/src/StorageStack.ts +++ b/src/StorageStack.ts @@ -1,18 +1,22 @@ -import { Duration, Stack } from 'aws-cdk-lib'; +import { Duration, Stack, StackProps } from 'aws-cdk-lib'; import { AttributeType, BillingMode, Table, TableEncryption } from 'aws-cdk-lib/aws-dynamodb'; import { Key } from 'aws-cdk-lib/aws-kms'; import { Bucket, BucketEncryption, ObjectOwnership } from 'aws-cdk-lib/aws-s3'; import { Secret } from 'aws-cdk-lib/aws-secretsmanager'; import { StringParameter } from 'aws-cdk-lib/aws-ssm'; import { Construct } from 'constructs'; +import { Configurable } from './Configuration'; import { Statics } from './statics'; + +interface StorageStackProps extends StackProps, Configurable {}; + /** * Contains all API-related resources. */ export class StorageStack extends Stack { - constructor(scope: Construct, id: string) { - super(scope, id); + constructor(scope: Construct, id: string, props: StorageStackProps) { + super(scope, id, props); const key = this.key(); /** @@ -63,7 +67,6 @@ export class StorageStack extends Stack { return key; } - private addArnToParameterStore(id: string, arn: string, name: string) { new StringParameter(this, id, { stringValue: arn, diff --git a/src/SubmissionSnsEventHandler.ts b/src/SubmissionSnsEventHandler.ts index 644f4abc..2a541753 100644 --- a/src/SubmissionSnsEventHandler.ts +++ b/src/SubmissionSnsEventHandler.ts @@ -2,6 +2,7 @@ import { Duration } from 'aws-cdk-lib'; import { ITable, Table } from 'aws-cdk-lib/aws-dynamodb'; import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; import { Key } from 'aws-cdk-lib/aws-kms'; +import { Function } from 'aws-cdk-lib/aws-lambda'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import { Bucket, IBucket } from 'aws-cdk-lib/aws-s3'; import { ISecret, Secret } from 'aws-cdk-lib/aws-secretsmanager'; @@ -13,14 +14,14 @@ import { SubmissionFunction } from './app/submission/submission-function'; import { Statics } from './statics'; interface SubmissionSnsEventHandlerProps { - topicArn: string; + topicArns: string[]; } export class SubmissionSnsEventHandler extends Construct { private role?: Role; + public lambda: Function; constructor(scope: Construct, id: string, props: SubmissionSnsEventHandlerProps) { super(scope, id); - const topic = Topic.fromTopicArn(this, 'submission-topic', props.topicArn); const table = Table.fromTableName(this, 'table', StringParameter.valueForStringParameter(this, Statics.ssmSubmissionTableName)); const key = Key.fromKeyArn(this, 'key', StringParameter.valueForStringParameter(this, Statics.ssmDataKeyArn)); // IBucket requires encryption key, otherwise grant methods won't add the correct permissions @@ -31,7 +32,8 @@ export class SubmissionSnsEventHandler extends Construct { const sourceBucket = Bucket.fromBucketArn(this, 'sourceBucket', StringParameter.valueForStringParameter(this, Statics.ssmSourceBucketArn)); const secret = Secret.fromSecretNameV2(this, 'apikey', Statics.secretFormIoApiKey); - this.submissionHandlerLambda(storageBucket, sourceBucket, table, topic, secret); + const topics = props.topicArns.map((topicArn, i)=> Topic.fromTopicArn(this, `submission-topic-${i}`, topicArn)); + this.lambda = this.submissionHandlerLambda(storageBucket, sourceBucket, table, topics, secret); } /** @@ -46,7 +48,7 @@ export class SubmissionSnsEventHandler extends Construct { * @param table The dynamodb table to store submission (meta)data in * @param topic The SNS Topic to subscribe to for submissions */ - private submissionHandlerLambda(bucket: IBucket, sourceBucket: IBucket, table: ITable, topic: ITopic, secret: ISecret) { + private submissionHandlerLambda(bucket: IBucket, sourceBucket: IBucket, table: ITable, topics: ITopic[], secret: ISecret) { const submissionLambda = new SubmissionFunction(this, 'submission', { role: this.lambdaRole(), logRetention: RetentionDays.SIX_MONTHS, @@ -65,7 +67,10 @@ export class SubmissionSnsEventHandler extends Construct { const key = Key.fromKeyArn(this, 'sourceBucketKey', StringParameter.valueForStringParameter(this, Statics.ssmSourceKeyArn)); key.grantDecrypt(submissionLambda); - topic.addSubscription(new LambdaSubscription(submissionLambda)); + for (const topic of topics) { + topic.addSubscription(new LambdaSubscription(submissionLambda)); + } + return submissionLambda; } /** diff --git a/src/app/submission/SubmissionSchema.ts b/src/app/submission/SubmissionSchema.ts index 41286ae8..773127cf 100644 --- a/src/app/submission/SubmissionSchema.ts +++ b/src/app/submission/SubmissionSchema.ts @@ -12,8 +12,8 @@ export const SubmissionSchema = z.object({ reference: z.string(), data: z.object({ kenmerk: z.string(), - naamIngelogdeGebruiker: z.string(), - }).required().passthrough(), + naamIngelogdeGebruiker: z.string().optional(), + }).passthrough(), employeeData: z.any(), pdf: z.object({ reference: z.string(), diff --git a/src/app/submission/test/samples/sns.sample-anonymous.json b/src/app/submission/test/samples/sns.sample-anonymous.json new file mode 100644 index 00000000..fa872f3a --- /dev/null +++ b/src/app/submission/test/samples/sns.sample-anonymous.json @@ -0,0 +1,34 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:eu-west-1: 315037222840:eform-submissions:293455ad-bb4b-4667-971d-7eaba358510d", + "Sns": { + "Type": "Notification", + "MessageId": "538c926d-27a7-567b-b314-cf7e54168743", + "TopicArn": "arn:aws:sns:eu-west-1:315037222840:eform-submissions", + "Message": "{\"formId\": \"82307c98-b8fa-4a31-825d-730496dd1e50\",\"formTypeId\": \"formnaam\",\"appId\": \"TDL\",\"reference\": \"TDL123.000\",\"data\": {\"telefoonnummer\": \"\",\"eMailadres\": \"me@example.com\",\"isIemandAndersUwContactpersoon\": \"nee\",\"kenmerk\": \"TDL123.000\",\"volgende\": true,\"waaroverHeeftUEenVraag\": {\"ikHebHulpNodigBijHetHuishouden\": true,\"ikHebHulpOfEenHulpmiddelNodigBijHetVervoer\": false,\"ikHebEenHulpmiddelOfAanpassingInMijnWoningNodig\": false,\"anders\": false},\"watIsUwVraag\": \"Graag hulp bij het huishouden.\",\"vorige1\": false,\"volgende1\": true,\"toevoegen\": [],\"vorige2\": false,\"volgende2\": true,\"summary1\": {},\"ikHebAlleVragenNaarWaarheidBeantwoord\": true,\"vorige3\": false,\"verzenden2\": true},\"metadata\": {\"timezone\": \"Europe/Amsterdam\",\"offset\": 120,\"origin\": \"https://app6-accp.nijmegen.nl\",\"referrer\": \"https://www.nijmegen.nl/\",\"browserName\": \"Netscape\",\"userAgent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36\",\"pathName\": \"/\",\"onLine\": true,\"timestamp\": [2023,8,25,12,20,47,238191797]},\"pdf\": {\"reference\": \"blaat\",\"location\": \"/pdf/TDL.1230\",\"bucketName\": \"mybucket\"}}", + "Timestamp": "2023-06-26T11:07:29.154Z", + "SignatureVersion": "1", + "Signature": "MP6SMqgHFUA3eWuR/PkRLKW/eLzsbGa81a7NVN7/8Fq94K9X9DgaiqMmWq1CwWNpR5K8ISd5lwE+MIBK1h19lFVhYa/QgbIWsys8G9pCvfHdI1s+xzEAfVxgrRPa71QHkGNrrxqJN5c78ahChN9IG/XRPddYqltei5ZX6BRdFwTc0pGLkCy0hNj6kqJ30VGT8jpJoo/+PFt0PXTs+NCaOSfUo+kxO2VKtgXmHElPzfb7VAV2QFGtVkcGBgnxA5o0eEwepjf844L/BC3YsHxqHLVeg9rreMtkvoAsETRo9Qy8anYC1TcQ4rkmyWaKp4QlSllidzZxjKtbgvuC5ZPHRQ==", + "SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem", + "UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:315037222840:eform-submissions:bb9d78bf-834b-4146-898c-ff3456b26f9e", + "MessageAttributes": { + "BRP_data": { + "Type": "String", + "Value": "true" + }, + "AppId": { + "Type": "String", + "Value": "APV" + }, + "KVK_data": { + "Type": "String", + "Value": "false" + } + } + } + } + ] +} diff --git a/src/app/submission/test/samples/sns.sample-duplicate-files.json b/src/app/submission/test/samples/sns.sample-duplicate-files.json new file mode 100644 index 00000000..81c17568 --- /dev/null +++ b/src/app/submission/test/samples/sns.sample-duplicate-files.json @@ -0,0 +1,34 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:eu-west-1: 315037222840:eform-submissions:293455ad-bb4b-4667-971d-7eaba358510d", + "Sns": { + "Type": "Notification", + "MessageId": "538c926d-27a7-567b-b314-cf7e54168743", + "TopicArn": "arn:aws:sns:eu-west-1:315037222840:eform-submissions", + "Message": "{\"formId\": \"68c11b91-d127-414f-89cc-c6ef7ab3e1bf\",\"formTypeId\": \"test\",\"appId\": \"TDL\",\"reference\": \"TDL17.957\",\"data\": {\"bsn\": \"900026236\",\"kvk\": \"\",\"achternaam\": \"\",\"betrouwbaarheidsniveau\": \"digid\",\"familienaam\": \"\",\"geboortedatum\": \"01-01-1956\",\"gemeente\": \"\",\"geslacht\": \"\",\"huisnummer\": \"\",\"initialen\": \"\",\"inlogmiddel\": \"digid\",\"kenmerk\": \"TDL-17.957\",\"naamIngelogdeGebruiker\": \"H. de Jong\",\"nationaliteit\": \"\",\"postcode\": \"\",\"straatnaam\": \"\",\"totaalbedrag\": \"\",\"tussenvoegsel\": \"\",\"vipZaakType\": \"\",\"volledigeNaam\": \"H. de Jong\",\"voornaam\": \"\",\"woonplaats\": \"Nijmegen\",\"adres_nijmegen\": {\"adres_nijmegen-postalcode\": \"6511 pp\",\"adres_nijmegen-housenr\": \"6\",\"adres_nijmegen-streetname\": \"Korte Nieuwstraat\",\"adres_nijmegen-city\": \"Nijmegen\"},\"select_nijmegen\": \"\",\"eMailadres\": \"\",\"selectBoxesNijmegen\": {\"test\": true,\"tweede\": false,\"derde\": false},\"test\": false,\"dataGrid\": [{\"textareaNijmegen\": \"\"}],\"fileNijmegen\": [{\"storage\": \"url\",\"name\": \"test-1m-14cfa303-e58a-42dc-bd47-1299a789a627.pdf\",\"url\": \"https://example.com/-test-1m-blaat.pdf\",\"size\": \"1048576\",\"type\": \"application/pdf\",\"reference\": \"360fa9d4e798e8902d19e9757eef63ad\",\"originalName\": \"test-1m-blaat.pdf\",\"location\": \"randomid/files/test-1m-blaat.pdf\",\"bucketName\": \"randombucket\"},{\"storage\": \"url\",\"name\": \"test-1m-1differentrandomid.pdf\",\"url\": \"https://example.com/-test-1m-blaat.pdf\",\"size\": \"1048576\",\"type\": \"application/pdf\",\"reference\": \"360fa9d4e798e8902d19e9757eef63ad\",\"originalName\": \"test-1m-blaat.pdf\",\"location\": \"randomdifferentid/files/test-1m-blaat.pdf\",\"bucketName\": \"randombucket\"},{\"storage\": \"url\",\"name\": \"test-1m-14cfa303-e58a-42dc-bd47-1299a789a627.pdf\",\"url\": \"https://example.com/-1299a789a627.pdf\",\"size\": \"1048576\",\"type\": \"application/pdf\",\"reference\": \"360fa9d4e798e8902d19e9757eef63ad\",\"originalName\": \"test-1m.pdf\",\"location\": \"randomid/files/test-1m.pdf\",\"bucketName\": \"randombucket\"}],\"opslaan\": false,\"volgende\": true,\"submit1\": false,\"submit\": true},\"metadata\": {\"timezone\": \"Europe/Amsterdam\",\"offset\": 120,\"origin\": \"https://app6-accp.nijmegen.nl\",\"referrer\": \"\",\"browserName\": \"Netscape\",\"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.15\",\"pathName\": \"/\",\"onLine\": true},\"employeeData\": {},\"pdf\": {\"reference\": \"c07e4f3b36955bb31565fb1ef7bdefaf\",\"location\": \"randomid/pdf/TDL17.957\",\"bucketName\": \"randombucket\"},\"brpData\": {\"Persoon\": {\"BSN\": {\"BSN\": \"900026236\"},\"Persoonsgegevens\": {\"Voorletters\": \"H.\",\"Voornamen\": \"Hans\",\"Voorvoegsel\": \"de\",\"Geslachtsnaam\": \"Jong\",\"Achternaam\": \"de Jong\",\"Naam\": \"H. de Jong\",\"Geboortedatum\": \"01-01-1956\",\"Geslacht\": \"M\",\"NederlandseNationaliteit\": \"Ja\",\"Geboorteplaats\": \"Nijmegen\",\"Geboorteland\": \"Nederland\"},\"Adres\": {\"Straat\": \"Kelfkensbos\",\"Huisnummer\": \"80-A-1\",\"Gemeente\": \"Nijmegen\",\"Postcode\": \"6511 RN\",\"Woonplaats\": \"Nijmegen\"},\"Reisdocument\": {\"Documentsoort\": \"\",\"Documentnummer\": \"\",\"Uitgiftedatum\": \"\",\"Verloopdatum\": \"\"},\"ageLimits\": {\"over12\": \"Yes\",\"over16\": \"Yes\",\"over18\": \"Yes\",\"over21\": \"Yes\",\"over65\": \"Yes\"}}},\"bsn\": \"900026236\"}", + "Timestamp": "2023-06-26T11:07:29.154Z", + "SignatureVersion": "1", + "Signature": "MP6SMqgHFUA3eWuR/PkRLKW/eLzsbGa81a7NVN7/8Fq94K9X9DgaiqMmWq1CwWNpR5K8ISd5lwE+MIBK1h19lFVhYa/QgbIWsys8G9pCvfHdI1s+xzEAfVxgrRPa71QHkGNrrxqJN5c78ahChN9IG/XRPddYqltei5ZX6BRdFwTc0pGLkCy0hNj6kqJ30VGT8jpJoo/+PFt0PXTs+NCaOSfUo+kxO2VKtgXmHElPzfb7VAV2QFGtVkcGBgnxA5o0eEwepjf844L/BC3YsHxqHLVeg9rreMtkvoAsETRo9Qy8anYC1TcQ4rkmyWaKp4QlSllidzZxjKtbgvuC5ZPHRQ==", + "SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem", + "UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1:315037222840:eform-submissions:bb9d78bf-834b-4146-898c-ff3456b26f9e", + "MessageAttributes": { + "BRP_data": { + "Type": "String", + "Value": "true" + }, + "AppId": { + "Type": "String", + "Value": "APV" + }, + "KVK_data": { + "Type": "String", + "Value": "false" + } + } + } + } + ] +} diff --git a/src/app/submission/test/submission.test.ts b/src/app/submission/test/submission.test.ts index 8f8bb181..022ef348 100644 --- a/src/app/submission/test/submission.test.ts +++ b/src/app/submission/test/submission.test.ts @@ -1,3 +1,4 @@ +import * as snsSampleAnonymous from './samples/sns.sample-anonymous.json'; import * as snsSample from './samples/sns.sample.json'; import { MockDatabase } from '../Database'; import { MockFormConnector } from '../FormConnector'; @@ -25,10 +26,11 @@ describe('Submission parsing', () => { }); test('Message without kvk or bsn key is anonymous', async () => { - const anonMessage = JSON.parse(JSON.stringify(message)); - anonMessage.Message = anonMessage.Message.replace(',\"bsn\":\"900222670\"', ''); + const messagesAnonymous = snsSampleAnonymous.Records.map(record => record.Sns); + const messageAnonymous = messagesAnonymous.pop(); + const anonMessage = JSON.parse(JSON.stringify(messageAnonymous)); const anonSubmission = new Submission({ storage, formConnector, database }); - await submission.parse(anonMessage); + await anonSubmission.parse(anonMessage); expect(anonSubmission.isAnonymous()).toBe(true); }); diff --git a/src/statics.ts b/src/statics.ts index 85a6349f..5d284b59 100644 --- a/src/statics.ts +++ b/src/statics.ts @@ -21,6 +21,8 @@ export abstract class Statics { region: 'eu-central-1', }; + static readonly acceptanceWebformulierenAccountId = '315037222840'; + static ssmDataKeyArn: string = `/${this.projectName}/dataKeyArn`; static ssmSubmissionBucketArn: string = `/${this.projectName}/submissionBucketArn`; static ssmSourceBucketArn: string = `/${this.projectName}/sourceBucketArn`; diff --git a/test/main.test.ts b/test/main.test.ts index 678d8773..7fe13bf8 100644 --- a/test/main.test.ts +++ b/test/main.test.ts @@ -1,24 +1,35 @@ import { App } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; +import { ApiStack } from '../src/ApiStack'; import { Configuration } from '../src/Configuration'; import { PipelineStack } from '../src/PipelineStack'; +const configuration: Configuration = { + branchName: 'stacktest', + deployFromEnvironment: { + account: '12345678', + region: 'eu-central-1', + }, + deployToEnvironment: { + account: '12345678', + region: 'eu-central-1', + }, + includePipelineValidationChecks: false, +}; + test('Snapshot', () => { const app = new App(); - const configuration: Configuration = { - branchName: 'stacktest', - deployFromEnvironment: { - account: '12345678', - region: 'eu-central-1', - }, - deployToEnvironment: { - account: '12345678', - region: 'eu-central-1', - }, - includePipelineValidationChecks: false, - }; + const stack = new PipelineStack(app, 'test', { env: configuration.deployFromEnvironment, configuration }); const template = Template.fromStack(stack); expect(template.toJSON()).toMatchSnapshot(); }); + + +test('Api Stack', () => { + const app = new App(); + const apiStack = new ApiStack(app, 'api', { configuration }); + const template = Template.fromStack(apiStack); + expect(template.resourceCountIs('AWS::SNS::Subscription', 2)); +});