Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(s3-assets): use jest for tests #8411

Merged
merged 2 commits into from
Jun 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-s3-assets/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ nyc.config.js
.LAST_PACKAGE
*.snk
!.eslintrc.js
!jest.config.js
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-s3-assets/.npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ dist

tsconfig.json
.eslintrc.js
jest.config.js
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const baseConfig = require('../../../tools/cdk-build-tools/config/jest.config');
module.exports = baseConfig;
14 changes: 4 additions & 10 deletions packages/@aws-cdk/aws-s3-assets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
"build+test": "npm run build && npm test",
"compat": "cdk-compat"
},
"cdk-build": {
"jest": true
},
"keywords": [
"aws",
"cdk",
Expand All @@ -60,16 +63,10 @@
"license": "Apache-2.0",
"devDependencies": {
"@aws-cdk/assert": "0.0.0",
"@types/nodeunit": "^0.0.31",
"@types/sinon": "^9.0.4",
"aws-cdk": "0.0.0",
"cdk-build-tools": "0.0.0",
"cdk-integ-tools": "0.0.0",
"nodeunit": "^0.11.3",
"pkglint": "0.0.0",
"sinon": "^9.0.2",
"@aws-cdk/cloud-assembly-schema": "0.0.0",
"ts-mock-imports": "^1.3.0"
"@aws-cdk/cloud-assembly-schema": "0.0.0"
},
"dependencies": {
"@aws-cdk/assets": "0.0.0",
Expand All @@ -93,9 +90,6 @@
},
"stability": "experimental",
"maturity": "experimental",
"nyc": {
"statements": 75
},
"awslint": {
"exclude": [
"docs-public-apis:@aws-cdk/aws-s3-assets.AssetOptions",
Expand Down
328 changes: 328 additions & 0 deletions packages/@aws-cdk/aws-s3-assets/test/asset.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
import { ResourcePart, SynthUtils } from '@aws-cdk/assert';
import '@aws-cdk/assert/jest';
import * as iam from '@aws-cdk/aws-iam';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import * as cdk from '@aws-cdk/core';
import * as cxapi from '@aws-cdk/cx-api';
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { Asset } from '../lib/asset';

const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory');

test('simple use case', () => {
const app = new cdk.App({
context: {
[cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true',
},
});
const stack = new cdk.Stack(app, 'MyStack');
new Asset(stack, 'MyAsset', {
path: SAMPLE_ASSET_DIR,
});

// verify that metadata contains an "aws:cdk:asset" entry with
// the correct information
const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset');
expect(entry).toBeTruthy();

// verify that now the template contains parameters for this asset
const session = app.synth();

expect(stack.resolve(entry!.data)).toEqual({
path: SAMPLE_ASSET_DIR,
id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
packaging: 'zip',
sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B',
s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9',
artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD',
});

const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), 'utf-8'));

expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B.Type).toBe('String');
expect(template.Parameters.AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9.Type).toBe('String');
});

test('verify that the app resolves tokens in metadata', () => {
const app = new cdk.App();
const stack = new cdk.Stack(app, 'my-stack');
const dirPath = path.resolve(__dirname, 'sample-asset-directory');

new Asset(stack, 'MyAsset', {
path: dirPath,
});

const synth = app.synth().getStackByName(stack.stackName);
const meta = synth.manifest.metadata || {};
expect(meta['/my-stack']).toBeTruthy();
expect(meta['/my-stack'][0]).toBeTruthy();
expect(meta['/my-stack'][0].data).toEqual({
path: 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
id: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
packaging: 'zip',
sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
s3BucketParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B',
s3KeyParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3VersionKey1F7D75F9',
artifactHashParameter: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2ArtifactHash220DE9BD',
});
});

test('"file" assets', () => {
const stack = new cdk.Stack();
const filePath = path.join(__dirname, 'file-asset.txt');
new Asset(stack, 'MyAsset', { path: filePath });
const entry = stack.node.metadata.find(m => m.type === 'aws:cdk:asset');
expect(entry).toBeTruthy();

// synthesize first so "prepare" is called
const template = SynthUtils.synthesize(stack).template;

expect(stack.resolve(entry!.data)).toEqual({
path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt',
packaging: 'file',
id: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197',
sourceHash: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197',
s3BucketParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A',
s3KeyParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35',
artifactHashParameter: 'AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197ArtifactHash22BFFA67',
});

// verify that now the template contains parameters for this asset
expect(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3Bucket2C60F94A.Type).toBe('String');
expect(template.Parameters.AssetParameters78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197S3VersionKey9482DC35.Type).toBe('String');
});

test('"readers" or "grantRead" can be used to grant read permissions on the asset to a principal', () => {
const stack = new cdk.Stack();
const user = new iam.User(stack, 'MyUser');
const group = new iam.Group(stack, 'MyGroup');

const asset = new Asset(stack, 'MyAsset', {
path: path.join(__dirname, 'sample-asset-directory'),
readers: [ user ],
});

asset.grantRead(group);

expect(stack).toHaveResource('AWS::IAM::Policy', {
PolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: ['s3:GetObject*', 's3:GetBucket*', 's3:List*'],
Effect: 'Allow',
Resource: [
{ 'Fn::Join': ['', ['arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'} ] ] },
{ 'Fn::Join': ['', [ 'arn:', {Ref: 'AWS::Partition'}, ':s3:::', {Ref: 'AssetParameters6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2S3Bucket50B5A10B'}, '/*' ] ] },
],
},
],
},
});
});

test('fails if directory not found', () => {
const stack = new cdk.Stack();
expect(() => new Asset(stack, 'MyDirectory', {
path: '/path/not/found/' + Math.random() * 999999,
})).toThrow();
});

test('multiple assets under the same parent', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
expect(() => new Asset(stack, 'MyDirectory1', { path: path.join(__dirname, 'sample-asset-directory') })).not.toThrow();
expect(() => new Asset(stack, 'MyDirectory2', { path: path.join(__dirname, 'sample-asset-directory') })).not.toThrow();
});

test('isZipArchive indicates if the asset represents a .zip file (either explicitly or via ZipDirectory packaging)', () => {
// GIVEN
const stack = new cdk.Stack();

// WHEN
const nonZipAsset = new Asset(stack, 'NonZipAsset', {
path: path.join(__dirname, 'sample-asset-directory', 'sample-asset-file.txt'),
});

const zipDirectoryAsset = new Asset(stack, 'ZipDirectoryAsset', {
path: path.join(__dirname, 'sample-asset-directory'),
});

const zipFileAsset = new Asset(stack, 'ZipFileAsset', {
path: path.join(__dirname, 'sample-asset-directory', 'sample-zip-asset.zip'),
});

const jarFileAsset = new Asset(stack, 'JarFileAsset', {
path: path.join(__dirname, 'sample-asset-directory', 'sample-jar-asset.jar'),
});

// THEN
expect(nonZipAsset.isZipArchive).toBe(false);
expect(zipDirectoryAsset.isZipArchive).toBe(true);
expect(zipFileAsset.isZipArchive).toBe(true);
expect(jarFileAsset.isZipArchive).toBe(true);
});

test('addResourceMetadata can be used to add CFN metadata to resources', () => {
// GIVEN
const stack = new cdk.Stack();
stack.node.setContext(cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT, true);

const location = path.join(__dirname, 'sample-asset-directory');
const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' });
const asset = new Asset(stack, 'MyAsset', { path: location });

// WHEN
asset.addResourceMetadata(resource, 'PropName');

// THEN
expect(stack).toHaveResource('My::Resource::Type', {
Metadata: {
'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
'aws:asset:property': 'PropName',
},
}, ResourcePart.CompleteDefinition);
});

test('asset metadata is only emitted if ASSET_RESOURCE_METADATA_ENABLED_CONTEXT is defined', () => {
// GIVEN
const stack = new cdk.Stack();

const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' });
const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR });

// WHEN
asset.addResourceMetadata(resource, 'PropName');

// THEN
expect(stack).not.toHaveResource('My::Resource::Type', {
Metadata: {
'aws:asset:path': SAMPLE_ASSET_DIR,
'aws:asset:property': 'PropName',
},
}, ResourcePart.CompleteDefinition);
});

describe('staging', () => {
test('copy file assets under <outdir>/${fingerprint}.ext', () => {
const tempdir = mkdtempSync();
process.chdir(tempdir); // change current directory to somewhere in /tmp

// GIVEN
const app = new cdk.App({ outdir: tempdir });
const stack = new cdk.Stack(app, 'stack');

// WHEN
new Asset(stack, 'ZipFile', {
path: path.join(SAMPLE_ASSET_DIR, 'sample-zip-asset.zip'),
});

new Asset(stack, 'TextFile', {
path: path.join(SAMPLE_ASSET_DIR, 'sample-asset-file.txt'),
});

// THEN
app.synth();
expect(fs.existsSync(tempdir)).toBe(true);
expect(fs.existsSync(path.join(tempdir, 'asset.a7a79cdf84b802ea8b198059ff899cffc095a1b9606e919f98e05bf80779756b.zip'))).toBe(true);
});

test('copy directory under .assets/fingerprint/**', () => {
const tempdir = mkdtempSync();
process.chdir(tempdir); // change current directory to somewhere in /tmp

// GIVEN
const app = new cdk.App({ outdir: tempdir });
const stack = new cdk.Stack(app, 'stack');

// WHEN
new Asset(stack, 'ZipDirectory', {
path: SAMPLE_ASSET_DIR,
});

// THEN
app.synth();
expect(fs.existsSync(tempdir)).toBe(true);
const hash = 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2';
expect(fs.existsSync(path.join(tempdir, hash, 'sample-asset-file.txt'))).toBe(true);
expect(fs.existsSync(path.join(tempdir, hash, 'sample-jar-asset.jar'))).toBe(true);
expect(() => fs.readdirSync(tempdir)).not.toThrow();
});

test('staging path is relative if the dir is below the working directory', () => {
// GIVEN
const tempdir = mkdtempSync();
process.chdir(tempdir); // change current directory to somewhere in /tmp

const staging = '.my-awesome-staging-directory';
const app = new cdk.App({
outdir: staging,
context: {
[cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true',
},
});

const stack = new cdk.Stack(app, 'stack');

const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' });
const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR });

// WHEN
asset.addResourceMetadata(resource, 'PropName');

const template = SynthUtils.synthesize(stack).template;
expect(template.Resources.MyResource.Metadata).toEqual({
'aws:asset:path': 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2',
'aws:asset:property': 'PropName',
});
});

test('if staging is disabled, asset path is absolute', () => {
// GIVEN
const staging = path.resolve(mkdtempSync());
const app = new cdk.App({
outdir: staging,
context: {
[cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true',
[cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true',
},
});

const stack = new cdk.Stack(app, 'stack');

const resource = new cdk.CfnResource(stack, 'MyResource', { type: 'My::Resource::Type' });
const asset = new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR });

// WHEN
asset.addResourceMetadata(resource, 'PropName');

const template = SynthUtils.synthesize(stack).template;
expect(template.Resources.MyResource.Metadata).toEqual({
'aws:asset:path': SAMPLE_ASSET_DIR,
'aws:asset:property': 'PropName',
});
});

test('cdk metadata points to staged asset', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'stack');
new Asset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR });

// WHEN
const session = app.synth();
const artifact = session.getStackByName(stack.stackName);
const metadata = artifact.manifest.metadata || {};
const md = Object.values(metadata)[0]![0]!.data as cxschema.AssetMetadataEntry;
expect(md.path).toBe('asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2');
});
});

function mkdtempSync() {
return fs.mkdtempSync(path.join(os.tmpdir(), 'assets.test'));
}
Loading