From 9d5f73af33a13fce8e74ce8f22e1d505f65fefe4 Mon Sep 17 00:00:00 2001 From: Justin Van Patten Date: Fri, 13 Dec 2024 07:12:25 -0800 Subject: [PATCH 1/3] Add unit tests to aws-python --- aws-python/.gitignore | 3 +++ aws-python/__main__.py | 10 +++----- aws-python/infra.py | 9 ++++++++ aws-python/requirements.txt | 1 + aws-python/tests/__init__.py | 0 aws-python/tests/test_infra.py | 42 ++++++++++++++++++++++++++++++++++ 6 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 aws-python/infra.py create mode 100644 aws-python/tests/__init__.py create mode 100644 aws-python/tests/test_infra.py diff --git a/aws-python/.gitignore b/aws-python/.gitignore index a3807e5bd..5510ab511 100644 --- a/aws-python/.gitignore +++ b/aws-python/.gitignore @@ -1,2 +1,5 @@ *.pyc +__pycache__ +.pytest_cache venv/ +.venv/ diff --git a/aws-python/__main__.py b/aws-python/__main__.py index f94233fe0..c934c2d4e 100644 --- a/aws-python/__main__.py +++ b/aws-python/__main__.py @@ -1,10 +1,6 @@ -"""An AWS Python Pulumi program""" - import pulumi -from pulumi_aws import s3 -# Create an AWS resource (S3 Bucket) -bucket = s3.BucketV2('my-bucket') +import infra -# Export the name of the bucket -pulumi.export('bucket_name', bucket.id) +# Export the name of the bucket. +pulumi.export("bucket_name", infra.bucket.id) diff --git a/aws-python/infra.py b/aws-python/infra.py new file mode 100644 index 000000000..8e41e09e1 --- /dev/null +++ b/aws-python/infra.py @@ -0,0 +1,9 @@ +import pulumi +from pulumi_aws import s3 + +# Create an AWS resource (S3 Bucket) with tags. +bucket = s3.BucketV2("my-bucket", + tags={ + "Name": "My bucket", + }, +) diff --git a/aws-python/requirements.txt b/aws-python/requirements.txt index 72aee7915..f2124ea15 100644 --- a/aws-python/requirements.txt +++ b/aws-python/requirements.txt @@ -1,2 +1,3 @@ pulumi>=3.0.0,<4.0.0 pulumi-aws>=6.0.2,<7.0.0 +pytest diff --git a/aws-python/tests/__init__.py b/aws-python/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/aws-python/tests/test_infra.py b/aws-python/tests/test_infra.py new file mode 100644 index 000000000..7ed82eec7 --- /dev/null +++ b/aws-python/tests/test_infra.py @@ -0,0 +1,42 @@ +import asyncio +from typing import TypeVar + +import pulumi + + +# Test helper to convert a Pulumi Output to a Future. +# This should only be used in tests. +T = TypeVar("T") +def future_of(output: pulumi.Output[T]) -> asyncio.Future[T]: + loop = asyncio.get_running_loop() + future = loop.create_future() + output.apply(lambda x: future.set_result(x)) + return future + + +class MyMocks(pulumi.runtime.Mocks): + # Mock calls to create new resources and return a canned response. + def new_resource(self, args: pulumi.runtime.MockResourceArgs): + # Here, we're returning a same-shaped object for all resource types. + # We could, however, use the arguments passed into this function to + # customize the mocked-out properties of a particular resource. + # See the unit-testing docs for details: + # https://www.pulumi.com/docs/iac/concepts/testing/unit/ + return [args.name + "_id", args.inputs] + + # Mock function calls and return an empty response. + def call(self, args: pulumi.runtime.MockCallArgs): + return {} + +# Put Pulumi in unit-test mode, mocking all calls to cloud-provider APIs. +pulumi.runtime.set_mocks(MyMocks()) + +# Now import the code that creates resources, and then test it. +import infra + +# Example test. To run, uncomment and run `python -m pytest --disable-pytest-warnings`. +# @pulumi.runtime.test +# async def test_bucket_tags(): +# tags = await future_of(infra.bucket.tags) +# assert tags, "bucket must have tags" +# assert "Name" in tags, "bucket must have a Name tag" From 819109b2cb4c089cc8cc873702c82cc6a2bdf98f Mon Sep 17 00:00:00 2001 From: Justin Van Patten Date: Fri, 13 Dec 2024 08:35:12 -0800 Subject: [PATCH 2/3] Add unit tests to aws-typescript --- aws-typescript/index.ts | 11 ++----- aws-typescript/infra.ts | 10 ++++++ aws-typescript/jest.config.js | 7 ++++ aws-typescript/package.json | 6 ++++ aws-typescript/tests/infra.spec.ts | 51 ++++++++++++++++++++++++++++++ aws-typescript/tsconfig.json | 5 +-- 6 files changed, 78 insertions(+), 12 deletions(-) create mode 100644 aws-typescript/infra.ts create mode 100644 aws-typescript/jest.config.js create mode 100644 aws-typescript/tests/infra.spec.ts diff --git a/aws-typescript/index.ts b/aws-typescript/index.ts index 7f3d91af9..6abe42f7d 100644 --- a/aws-typescript/index.ts +++ b/aws-typescript/index.ts @@ -1,9 +1,4 @@ -import * as pulumi from "@pulumi/pulumi"; -import * as aws from "@pulumi/aws"; -import * as awsx from "@pulumi/awsx"; +import * as infra from "./infra"; -// Create an AWS resource (S3 Bucket) -const bucket = new aws.s3.BucketV2("my-bucket"); - -// Export the name of the bucket -export const bucketName = bucket.id; +// Export the name of the bucket. +export const bucketName = infra.bucket.id; diff --git a/aws-typescript/infra.ts b/aws-typescript/infra.ts new file mode 100644 index 000000000..31e47c107 --- /dev/null +++ b/aws-typescript/infra.ts @@ -0,0 +1,10 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as aws from "@pulumi/aws"; +import * as awsx from "@pulumi/awsx"; + +// Create an AWS resource (S3 Bucket) with tags. +export const bucket = new aws.s3.BucketV2("my-bucket", { + tags: { + "Name": "My bucket", + }, +}); diff --git a/aws-typescript/jest.config.js b/aws-typescript/jest.config.js new file mode 100644 index 000000000..11a87cbf3 --- /dev/null +++ b/aws-typescript/jest.config.js @@ -0,0 +1,7 @@ +/** @type {import('ts-jest').JestConfigWithTsJest} **/ +module.exports = { + testEnvironment: "node", + transform: { + "^.+.tsx?$": ["ts-jest",{}], + }, +}; diff --git a/aws-typescript/package.json b/aws-typescript/package.json index a68c103f0..a36bc2318 100644 --- a/aws-typescript/package.json +++ b/aws-typescript/package.json @@ -2,12 +2,18 @@ "name": "${PROJECT}", "main": "index.ts", "devDependencies": { + "@types/jest": "^29.5.14", "@types/node": "^18", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", "typescript": "^5.0.0" }, "dependencies": { "@pulumi/aws": "^6.0.0", "@pulumi/awsx": "^2.0.2", "@pulumi/pulumi": "^3.113.0" + }, + "scripts": { + "test": "jest" } } diff --git a/aws-typescript/tests/infra.spec.ts b/aws-typescript/tests/infra.spec.ts new file mode 100644 index 000000000..277af996f --- /dev/null +++ b/aws-typescript/tests/infra.spec.ts @@ -0,0 +1,51 @@ +import * as pulumi from "@pulumi/pulumi"; +import "jest"; + +// Test helper to convert a Pulumi Output to a Promise. +// This should only be used in tests. +function promiseOf(output: pulumi.Output): Promise { + return new Promise(resolve => output.apply(resolve)); +} + +describe("infrastructure", () => { + // Define the infra variable as a type whose shape matches the that of the + // to-be-defined infra module. + let infra: typeof import("../infra"); + + beforeAll(() => { + // Put Pulumi in unit-test mode, mocking all calls to cloud-provider APIs. + pulumi.runtime.setMocks({ + // Mock calls to create new resources and return a canned response. + newResource: (args: pulumi.runtime.MockResourceArgs) => { + // Here, we're returning a same-shaped object for all resource types. + // We could, however, use the arguments passed into this function to + // customize the mocked-out properties of a particular resource. + // See the unit-testing docs for details: + // https://www.pulumi.com/docs/iac/concepts/testing/unit/ + return { + id: `${args.name}-id`, + state: args.inputs, + }; + }, + + // Mock function calls and return an empty response. + call: (args: pulumi.runtime.MockCallArgs) => { + return {}; + }, + }); + }); + + beforeEach(async () => { + // Dynamically import the infra module. + infra = await import("../infra"); + }); + + // Example test. To run, uncomment and run `npm test`. + // describe("bucket", () => { + // it("must have a name tag", async () => { + // const tags = await promiseOf(infra.bucket.tags); + // expect(tags).toBeDefined(); + // expect(tags).toHaveProperty("Name"); + // }); + // }); +}); diff --git a/aws-typescript/tsconfig.json b/aws-typescript/tsconfig.json index f960d5171..de9ce8397 100644 --- a/aws-typescript/tsconfig.json +++ b/aws-typescript/tsconfig.json @@ -11,8 +11,5 @@ "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.ts" - ] + } } From 59eceb15673bb4b05b7434d7580b4a8a2f5a2427 Mon Sep 17 00:00:00 2001 From: Justin Van Patten Date: Fri, 13 Dec 2024 09:33:32 -0800 Subject: [PATCH 3/3] Add unit tests to aws-go --- aws-go/go.mod | 1 + aws-go/main.go | 27 ++++++++++++++++++++---- aws-go/main_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 aws-go/main_test.go diff --git a/aws-go/go.mod b/aws-go/go.mod index 98928791b..37c72b216 100644 --- a/aws-go/go.mod +++ b/aws-go/go.mod @@ -5,4 +5,5 @@ go 1.20 require ( github.com/pulumi/pulumi-aws/sdk/v6 v6.37.1 github.com/pulumi/pulumi/sdk/v3 v3.117.0 + github.com/stretchr/testify v1.9.0 ) diff --git a/aws-go/main.go b/aws-go/main.go index da1806b3a..dc37bc821 100644 --- a/aws-go/main.go +++ b/aws-go/main.go @@ -5,16 +5,35 @@ import ( "github.com/pulumi/pulumi/sdk/v3/go/pulumi" ) +type infrastructure struct { + bucket *s3.BucketV2 +} + +func createInfrastructure(ctx *pulumi.Context) (*infrastructure, error) { + // Create an AWS resource (S3 Bucket) with tags. + bucket, err := s3.NewBucketV2(ctx, "my-bucket", &s3.BucketV2Args{ + Tags: pulumi.StringMap{ + "Name": pulumi.String("My bucket"), + }, + }) + if err != nil { + return nil, err + } + + return &infrastructure{ + bucket: bucket, + }, nil +} + func main() { pulumi.Run(func(ctx *pulumi.Context) error { - // Create an AWS resource (S3 Bucket) - bucket, err := s3.NewBucketV2(ctx, "my-bucket", nil) + infra, err := createInfrastructure(ctx) if err != nil { return err } - // Export the name of the bucket - ctx.Export("bucketName", bucket.ID()) + // Export the name of the bucket. + ctx.Export("bucketName", infra.bucket.ID()) return nil }) } diff --git a/aws-go/main_test.go b/aws-go/main_test.go new file mode 100644 index 000000000..1a1842480 --- /dev/null +++ b/aws-go/main_test.go @@ -0,0 +1,50 @@ +package main + +import ( + "sync" + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/stretchr/testify/assert" +) + +type mocks int + +// Mock calls to create new resources and return a canned response. +func (mocks) NewResource(args pulumi.MockResourceArgs) (string, resource.PropertyMap, error) { + // Here, we're returning a same-shaped object for all resource types. + // We could, however, use the arguments passed into this function to + // customize the mocked-out properties of a particular resource. + // See the unit-testing docs for details: + // https://www.pulumi.com/docs/iac/concepts/testing/unit/ + return args.Name + "_id", args.Inputs, nil +} + +// Mock function calls and return an empty response. +func (mocks) Call(args pulumi.MockCallArgs) (resource.PropertyMap, error) { + return resource.PropertyMap{}, nil +} + +func TestInfrastructure(t *testing.T) { + err := pulumi.RunErr(func(ctx *pulumi.Context) error { + var wg sync.WaitGroup + + // Example test. To run, uncomment and run `go test`. + // infra, err := createInfrastructure(ctx) + // assert.NoError(t, err) + + // wg.Add(1) + // infra.bucket.Tags.ApplyT(func(tags map[string]string) error { + // assert.NotNil(t, tags) + // assert.Contains(t, tags, "Name") + + // wg.Done() + // return nil + // }) + + wg.Wait() + return nil + }, pulumi.WithMocks("project", "stack", mocks(0))) + assert.NoError(t, err) +}