1+ import { existsSync , statSync , mkdtempSync , copyFileSync } from 'fs' ;
2+ import { tmpdir } from 'os' ;
3+ import { join , basename , extname } from 'path' ;
14import { describe , it , expect , vi , beforeEach } from 'vitest' ;
25import { ArtifactExporter } from '../../../src/artifactexporter/ArtifactExporter' ;
36import { DocumentType } from '../../../src/document/Document' ;
47import { S3Service } from '../../../src/services/S3Service' ;
58
69vi . mock ( '../../../src/services/S3Service' ) ;
10+ vi . mock ( 'fs' ) ;
11+ vi . mock ( 'os' ) ;
12+ vi . mock ( 'path' ) ;
13+ vi . mock ( 'archiver' ) ;
714
815describe ( 'ArtifactExporter' , ( ) => {
916 let mockS3Service : S3Service ;
1017
18+ const BASIC_TEMPLATE = 'Resources:\n Bucket:\n Type: AWS::S3::Bucket' ;
19+
20+ const LAMBDA_TEMPLATE = `
21+ Resources:
22+ MyFunction:
23+ Type: AWS::Lambda::Function
24+ Properties:
25+ Code: ./src/lambda
26+ Runtime: nodejs18.x
27+ ` ;
28+
29+ const SERVERLESS_TEMPLATE = `
30+ Resources:
31+ MyFunction:
32+ Type: AWS::Serverless::Function
33+ Properties:
34+ CodeUri: ./code
35+ Runtime: python3.9
36+ ` ;
37+
38+ const S3_URL_TEMPLATE = `
39+ Resources:
40+ MyFunction:
41+ Type: AWS::Lambda::Function
42+ Properties:
43+ Code: s3://existing-bucket/code.zip
44+ Runtime: nodejs18.x
45+ ` ;
46+
1147 beforeEach ( ( ) => {
1248 vi . clearAllMocks ( ) ;
1349 mockS3Service = {
1450 putObjectContent : vi . fn ( ) ,
15- putObject : vi . fn ( ) ,
51+ putObject : vi . fn ( ) . mockResolvedValue ( { VersionId : 'v123' } ) ,
1652 } as any ;
53+
54+ vi . mocked ( existsSync ) . mockReturnValue ( true ) ;
55+ vi . mocked ( statSync ) . mockReturnValue ( {
56+ isFile : ( ) => true ,
57+ isDirectory : ( ) => false ,
58+ } as any ) ;
59+ vi . mocked ( tmpdir ) . mockReturnValue ( '/tmp' ) ;
60+ vi . mocked ( join ) . mockImplementation ( ( ...args ) => args . join ( '/' ) ) ;
61+ vi . mocked ( basename ) . mockImplementation ( ( path ) => path ?. split ( '/' ) . pop ( ) ?? '' ) ;
62+ vi . mocked ( extname ) . mockImplementation ( ( path ) => {
63+ if ( ! path ) return '' ;
64+ const parts = path . split ( '.' ) ;
65+ return parts . length > 1 ? '.' + parts [ parts . length - 1 ] : '' ;
66+ } ) ;
67+ vi . mocked ( mkdtempSync ) . mockReturnValue ( '/tmp/cfn-123' ) ;
68+ vi . mocked ( copyFileSync ) . mockImplementation ( ( ) => { } ) ;
69+ } ) ;
70+
71+ describe ( 'getTemplateArtifacts' , ( ) => {
72+ it ( 'should identify Lambda function artifacts' , ( ) => {
73+ const exporter = new ArtifactExporter (
74+ mockS3Service ,
75+ DocumentType . YAML ,
76+ 'file:///template.yaml' ,
77+ LAMBDA_TEMPLATE ,
78+ ) ;
79+
80+ const artifacts = exporter . getTemplateArtifacts ( ) ;
81+ expect ( artifacts ) . toEqual ( [
82+ {
83+ resourceType : 'AWS::Lambda::Function' ,
84+ filePath : './src/lambda' ,
85+ } ,
86+ ] ) ;
87+ } ) ;
88+
89+ it ( 'should identify Serverless function artifacts' , ( ) => {
90+ const exporter = new ArtifactExporter (
91+ mockS3Service ,
92+ DocumentType . YAML ,
93+ 'file:///template.yaml' ,
94+ SERVERLESS_TEMPLATE ,
95+ ) ;
96+
97+ const artifacts = exporter . getTemplateArtifacts ( ) ;
98+ expect ( artifacts ) . toEqual ( [
99+ {
100+ resourceType : 'AWS::Serverless::Function' ,
101+ filePath : './code' ,
102+ } ,
103+ ] ) ;
104+ } ) ;
17105 } ) ;
18106
19- describe ( 'ArtifactExporter ' , ( ) => {
107+ describe ( 'export ' , ( ) => {
20108 it ( 'should create template with valid parameters' , ( ) => {
21109 const template = new ArtifactExporter (
22110 mockS3Service ,
23111 DocumentType . YAML ,
24112 'file:///path/to/template.yaml' ,
25- 'Resources:\n Bucket:\n Type: AWS::S3::Bucket' ,
113+ BASIC_TEMPLATE ,
26114 ) ;
27115 expect ( template ) . toBeDefined ( ) ;
28116 } ) ;
@@ -32,10 +120,59 @@ describe('ArtifactExporter', () => {
32120 mockS3Service ,
33121 DocumentType . YAML ,
34122 'file:///path/to/template.yaml' ,
35- 'Resources:\n Bucket:\n Type: AWS::S3::Bucket' ,
123+ BASIC_TEMPLATE ,
36124 ) ;
37125 const result = await template . export ( 'test-bucket' ) ;
38126 expect ( result ) . toBeDefined ( ) ;
39127 } ) ;
128+
129+ it ( 'should update Lambda function Code to S3 reference' , async ( ) => {
130+ const exporter = new ArtifactExporter (
131+ mockS3Service ,
132+ DocumentType . YAML ,
133+ 'file:///template.yaml' ,
134+ LAMBDA_TEMPLATE ,
135+ ) ;
136+
137+ const result = await exporter . export ( 'test-bucket' ) ;
138+
139+ const resources = ( result as any ) . Resources ;
140+ expect ( resources . MyFunction . Properties . Code ) . toEqual ( {
141+ S3Bucket : 'test-bucket' ,
142+ S3Key : expect . stringMatching ( / ^ a r t i f a c t \/ c f n - 1 2 3 - \d + $ / ) ,
143+ S3ObjectVersion : 'v123' ,
144+ } ) ;
145+ expect ( resources . MyFunction . Properties . Runtime ) . toBe ( 'nodejs18.x' ) ;
146+ } ) ;
147+
148+ it ( 'should update Serverless function CodeUri to S3 URL' , async ( ) => {
149+ const exporter = new ArtifactExporter (
150+ mockS3Service ,
151+ DocumentType . YAML ,
152+ 'file:///template.yaml' ,
153+ SERVERLESS_TEMPLATE ,
154+ ) ;
155+
156+ const result = await exporter . export ( 'my-bucket' ) ;
157+
158+ const resources = ( result as any ) . Resources ;
159+ expect ( resources . MyFunction . Properties . CodeUri ) . toMatch ( / ^ s 3 : \/ \/ m y - b u c k e t \/ a r t i f a c t \/ c f n - 1 2 3 - \d + $ / ) ;
160+ expect ( resources . MyFunction . Properties . Runtime ) . toBe ( 'python3.9' ) ;
161+ } ) ;
162+
163+ it ( 'should not modify existing S3 URLs' , async ( ) => {
164+ const exporter = new ArtifactExporter (
165+ mockS3Service ,
166+ DocumentType . YAML ,
167+ 'file:///template.yaml' ,
168+ S3_URL_TEMPLATE ,
169+ ) ;
170+
171+ const result = await exporter . export ( 'test-bucket' ) ;
172+
173+ const resources = ( result as any ) . Resources ;
174+ expect ( resources . MyFunction . Properties . Code ) . toBe ( 's3://existing-bucket/code.zip' ) ;
175+ expect ( mockS3Service . putObject ) . not . toHaveBeenCalled ( ) ;
176+ } ) ;
40177 } ) ;
41178} ) ;
0 commit comments