Unify unit tests for the guide
MODIFY
Examples.sln
Examples.sln
@@ -47,6 +47,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kubernetes.Guestbook", "kub
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kubernetes.Guestbook.Components", "kubernetes-cs-guestbook\components\Kubernetes.Guestbook.Components.csproj", "{8836BAE0-2093-44F0-89E2-3F233CB72CCC}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTesting", "testing-unit-cs\UnitTesting.csproj", "{7A9FC7DC-DC35-4033-B4FF-E9EFB3E6EBB8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -141,6 +143,10 @@ Global
{8836BAE0-2093-44F0-89E2-3F233CB72CCC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8836BAE0-2093-44F0-89E2-3F233CB72CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8836BAE0-2093-44F0-89E2-3F233CB72CCC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7A9FC7DC-DC35-4033-B4FF-E9EFB3E6EBB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7A9FC7DC-DC35-4033-B4FF-E9EFB3E6EBB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7A9FC7DC-DC35-4033-B4FF-E9EFB3E6EBB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7A9FC7DC-DC35-4033-B4FF-E9EFB3E6EBB8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

MODIFY
README.md
README.md
@@ -364,7 +364,7 @@ Example | Description |
Example | Description |
----- | --------- |
[Unit Tests in TypeScript](testing-unit-ts) | Mock-based unit tests in TypeScript.
-[Unit Tests in Python](testing-unit-python) | Mock-based unit tests in Python.
+[Unit Tests in Python](testing-unit-py) | Mock-based unit tests in Python.
[Unit Tests in Go](testing-unit-go) | Mock-based unit tests in Go.
[Unit Tests in C#](testing-unit-cs) | Mock-based unit tests in C#.
[Testing with Policies](testing-pac-ts) | Tests based on Policy-as-Code in TypeScript.

DELETE
testing-unit-cs/ArchiveFunctionApp.cs
-// Copyright 2016-2020, Pulumi Corporation
-
-using Pulumi;
-using Pulumi.Azure.AppService;
-using Pulumi.Azure.AppService.Inputs;
-using Pulumi.Azure.Storage;
-
-/// <summary>
-/// A simple component to be tested.
-/// </summary>
-public class ArchiveFunctionApp : ComponentResource
-{
- public ArchiveFunctionApp(string name, ArchiveFunctionAppArgs args, ComponentResourceOptions? options = null)
- : base("examples:azure:ArchiveFunctionApp", name, options)
- {
- var opts = new CustomResourceOptions { Parent = this };
-
- var storageAccount = new Account("sa", new AccountArgs
- {
- ResourceGroupName = args.ResourceGroupName,
- AccountReplicationType = "LRS",
- AccountTier = "Standard",
- }, opts);
-
- var appServicePlan = new Plan("asp", new PlanArgs
- {
- ResourceGroupName = args.ResourceGroupName,
- Kind = "FunctionApp",
- Sku = new PlanSkuArgs
- {
- Tier = "Dynamic",
- Size = "Y1",
- },
- }, opts);
-
- var container = new Container("zips", new ContainerArgs
- {
- StorageAccountName = storageAccount.Name,
- ContainerAccessType = "private",
- }, opts);
-
- var blob = new Blob("zip", new BlobArgs
- {
- StorageAccountName = storageAccount.Name,
- StorageContainerName = container.Name,
- Type = "Block",
- Source = args.Archive
- }, opts);
-
- var codeBlobUrl = SharedAccessSignature.SignedBlobReadUrl(blob, storageAccount);
-
- var app = new FunctionApp("app", new FunctionAppArgs
- {
- ResourceGroupName = args.ResourceGroupName,
- AppServicePlanId = appServicePlan.Id,
- AppSettings =
- {
- {"runtime", "dotnet"},
- {"WEBSITE_RUN_FROM_PACKAGE", codeBlobUrl},
- },
- StorageConnectionString = storageAccount.PrimaryConnectionString,
- Version = "~2"
- }, opts);
- }
-}
-
-public enum Runtime
-{
- DotNet,
- NodeJS,
- Python
-}
-
-public class ArchiveFunctionAppArgs
-{
- public Input<string> ResourceGroupName { get; set; } = null!;
- public Input<AssetOrArchive> Archive { get; set; } = null!;
- public Input<Runtime> Runtime { get; set; } = null!;
-}

DELETE
testing-unit-cs/ArchiveFunctionAppTests.cs
-using System.Linq;
-using System.Threading.Tasks;
-using FluentAssertions;
-using NUnit.Framework;
-using Pulumi;
-using Pulumi.Azure.AppService;
-using Pulumi.Azure.Core;
-
-namespace UnitTesting
-{
- /// <summary>
- /// Unit testing examples.
- /// </summary>
- [TestFixture]
- public class ArchiveFunctionAppTests
- {
- [Test]
- public async Task AllDefaults_CreatesConsumptionPlan()
- {
- var resources = await Testing.RunAsync<BasicStack>();
- resources.Length.Should().BeGreaterThan(1);
-
- var consumptionPlan = resources.OfType<Plan>().FirstOrDefault();
- consumptionPlan.Should().NotBeNull("App Service Plan not found");
-
- var kind = await consumptionPlan.Kind.GetValueAsync();
- kind.Should().Be("FunctionApp");
- }
-
- class BasicStack : Stack
- {
- public BasicStack()
- {
- var resourceGroup = new ResourceGroup("testrg");
- var functionApp = new ArchiveFunctionApp("app", new ArchiveFunctionAppArgs
- {
- ResourceGroupName = resourceGroup.Name
- });
- }
- }
- }
-}

MODIFY
testing-unit-cs/Testing.cs
testing-unit-cs/Testing.cs
// Copyright 2016-2020, Pulumi Corporation
+using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Moq;
@@ -21,10 +22,10 @@ namespace UnitTesting
var mocks = new Mock<IMocks>();
mocks.Setup(m => m.NewResourceAsync(It.IsAny<string>(), It.IsAny<string>(),
It.IsAny<ImmutableDictionary<string, object>>(), It.IsAny<string>(), It.IsAny<string>()))
- .ReturnsAsync((string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id) => (id ?? "", inputs));
+ .ReturnsAsync((string type, string name, ImmutableDictionary<string, object> inputs, string? provider, string? id) => (name + "_id", inputs));
mocks.Setup(m => m.CallAsync(It.IsAny<string>(), It.IsAny<ImmutableDictionary<string, object>>(), It.IsAny<string>()))
.ReturnsAsync((string token, ImmutableDictionary<string, object> args, string? provider) => args);
- return Deployment.TestAsync<T>(mocks.Object);
+ return Deployment.TestAsync<T>(mocks.Object, new TestOptions { IsPreview = false });
}
/// <summary>

MODIFY
testing-unit-cs/UnitTesting.csproj
testing-unit-cs/UnitTesting.csproj
<PackageReference Include="Moq" Version="4.13.1" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
- <PackageReference Include="Pulumi" Version="1.12.1-preview" />
- <PackageReference Include="Pulumi.Azure" Version="2.1.0-preview" />
+ <PackageReference Include="Pulumi.Aws" Version="1.27.0-preview" />
</ItemGroup>
</Project>

ADD
testing-unit-cs/WebserverStack.cs
+// Copyright 2016-2020, Pulumi Corporation
+
+using Pulumi;
+using Pulumi.Aws.Ec2;
+using Pulumi.Aws.Ec2.Inputs;
+
+/// <summary>
+/// A simple stack to be tested.
+/// </summary>
+public class WebserverStack : Stack
+{
+ public WebserverStack()
+ {
+ var group = new SecurityGroup("web-secgrp", new SecurityGroupArgs
+ {
+ Ingress =
+ {
+ // Uncomment to fail a test:
+ // new SecurityGroupIngressArgs { Protocol = "tcp", FromPort = 22, ToPort = 22, CidrBlocks = { "0.0.0.0/0" } },
+ new SecurityGroupIngressArgs { Protocol = "tcp", FromPort = 80, ToPort = 80, CidrBlocks = { "0.0.0.0/0" } }
+ }
+ });
+
+ // var userData = "#!/bin/bash echo \"Hello, World!\" > index.html nohup python -m SimpleHTTPServer 80 &";
+
+ var server = new Instance("web-server-www", new InstanceArgs
+ {
+ InstanceType = "t2.micro",
+ SecurityGroups = { group.Name }, // reference the group object above
+ Ami = "ami-c55673a0", // AMI for us-east-2 (Ohio),
+ // Comment out to fail a test:
+ Tags = { { "Name", "webserver" }},
+ // Uncomment to fail a test:
+ // UserData = userData // start a simple webserver
+ });
+ }
+}

ADD
testing-unit-cs/WebserverStackTests.cs
+using System.Linq;
+using System.Threading.Tasks;
+using FluentAssertions;
+using NUnit.Framework;
+using Pulumi.Aws.Ec2;
+
+namespace UnitTesting
+{
+ /// <summary>
+ /// Unit testing examples.
+ /// </summary>
+ [TestFixture]
+ public class WebserverStackTests
+ {
+ [Test]
+ public async Task InstanceHasNameTag()
+ {
+ var resources = await Testing.RunAsync<WebserverStack>();
+
+ var instance = resources.OfType<Instance>().FirstOrDefault();
+ instance.Should().NotBeNull("EC2 Instance not found");
+
+ var tags = await instance.Tags.GetValueAsync();
+ tags.Should().NotBeNull("Tags are not defined");
+ tags.Should().ContainKey("Name");
+ }
+
+ [Test]
+ public async Task InstanceMustNotUseInlineUserData()
+ {
+ var resources = await Testing.RunAsync<WebserverStack>();
+
+ var instance = resources.OfType<Instance>().FirstOrDefault();
+ instance.Should().NotBeNull("EC2 Instance not found");
+
+ var tags = await instance.UserData.GetValueAsync();
+ tags.Should().BeNull();
+ }
+
+ [Test]
+ public async Task SecurityGroupMustNotHaveSshPortsOpenToInternet()
+ {
+ var resources = await Testing.RunAsync<WebserverStack>();
+
+ foreach (var securityGroup in resources.OfType<SecurityGroup>())
+ {
+ var urn = await securityGroup.Urn.GetValueAsync();
+ var ingress = await securityGroup.Ingress.GetValueAsync();
+ foreach (var rule in ingress)
+ {
+ (rule.FromPort == 22 && rule.CidrBlocks.Any(b => b == "0.0.0.0/0"))
+ .Should().BeFalse($"Illegal SSH port 22 open to the Internet (CIDR 0.0.0.0/0) on group {urn}");
+ }
+ }
+ }
+ }
+}

MODIFY
testing-unit-py/README.md
testing-unit-py/README.md
@@ -7,7 +7,7 @@ An example of writing mock-based unit tests with both infrastructure definition
1. Create a Python virtualenv, activate it, and install dependencies:
```bash
- $ virtualenv -p python3 venv
+ $ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt
```

MODIFY
testing-unit-py/__main__.py
testing-unit-py/__main__.py
import pulumi
-import resources
+import infra
-pulumi.export("outprop", resources.mycomponent.outprop)
-pulumi.export("public_ip", resources.myinstance.public_ip)
+pulumi.export('group', infra.group)
+pulumi.export('server', infra.server)
+pulumi.export('publicIp', infra.server.public_ip)
+pulumi.export('publicHostName', infra.server.public_dns)

ADD
testing-unit-py/infra.py
+import pulumi
+from pulumi_aws import ec2
+
+group = ec2.SecurityGroup('web-secgrp', ingress=[
+ # Uncomment to fail a test:
+ #{ "protocol": "tcp", "from_port": 22, "to_port": 22, "cidr_blocks": ["0.0.0.0/0"] },
+ { "protocol": "tcp", "from_port": 80, "to_port": 80, "cidr_blocks": ["0.0.0.0/0"] },
+])
+
+user_data = '#!/bin/bash echo "Hello, World!" > index.html nohup python -m SimpleHTTPServer 80 &'
+
+server = ec2.Instance('web-server-www;',
+ instance_type="t2.micro",
+ security_groups=[ group.name ], # reference the group object above
+ # Comment out to fail a test:
+ tags={'Name': 'webserver'}, # name tag
+ ami="ami-c55673a0") # AMI for us-east-2 (Ohio)
+ # Uncomment to fail a test:
+ #user_data=user_data) # start a simple web server

DELETE
testing-unit-py/resources.py
-import pulumi
-from pulumi import Output
-
-class MyComponent(pulumi.ComponentResource):
- outprop: pulumi.Output[str]
- def __init__(self, name, inprop: pulumi.Input[str] = None, opts = None):
- super().__init__('pkg:index:MyComponent', name, None, opts)
- if inprop is None:
- raise TypeError("Missing required property 'inprop'")
- self.outprop = pulumi.Output.from_input(inprop).apply(lambda x: f"output: {x}")
-
-class Instance(pulumi.CustomResource):
- public_ip: pulumi.Output[str]
- def __init__(self, resource_name, name: pulumi.Input[str] = None, opts = None):
- if name is None:
- raise TypeError("Missing required property 'name'")
- __props__: dict = dict()
- __props__["public_ip"] = None
- __props__["name"] = name
- super(Instance, self).__init__('aws:ec2/instance:Instance', resource_name, __props__, opts)
-
-mycomponent = MyComponent("mycomponent", inprop="hello")
-myinstance = Instance("instance", name="myvm")

ADD
testing-unit-py/test_ec2.py
+import unittest
+import pulumi
+
+class MyMocks(pulumi.runtime.Mocks):
+ def new_resource(self, type_, name, inputs, provider, id_):
+ return [name + '_id', inputs]
+ def call(self, token, args, provider):
+ return {}
+
+pulumi.runtime.set_mocks(MyMocks())
+
+# Now actually import the code that creates resources, and then test it.
+import infra
+
+class TestingWithMocks(unittest.TestCase):
+ # Test if the service has tags and a name tag.
+ @pulumi.runtime.test
+ def test_server_tags(self):
+ def check_tags(args):
+ urn, tags = args
+ self.assertIsNotNone(tags, f'server {urn} must have tags')
+ self.assertIn('Name', tags, 'server {urn} must have a name tag')
+
+ return pulumi.Output.all(infra.server.urn, infra.server.tags).apply(check_tags)
+
+ # Test if the instance is configured with user_data.
+ @pulumi.runtime.test
+ def test_server_userdata(self):
+ def check_user_data(args):
+ urn, user_data = args
+ self.assertFalse(user_data, f'illegal use of user_data on server {urn}')
+
+ return pulumi.Output.all(infra.server.urn, infra.server.user_data).apply(check_user_data)
+
+ # Test if port 22 for ssh is exposed.
+ @pulumi.runtime.test
+ def test_security_group_rules(self):
+ def check_security_group_rules(args):
+ urn, ingress = args
+ ssh_open = any([rule['from_port'] == 22 and any([block == "0.0.0.0/0" for block in rule['cidr_blocks']]) for rule in ingress])
+ self.assertFalse(ssh_open, f'security group {urn} exposes port 22 to the Internet (CIDR 0.0.0.0/0)')
+
+ # Return the results of the unit tests.
+ return pulumi.Output.all(infra.group.urn, infra.group.ingress).apply(check_security_group_rules)

DELETE
testing-unit-py/test_testing_with_mocks.py
-import unittest
-import pulumi
-
-class MyMocks(pulumi.runtime.Mocks):
- def call(self, token, args, provider):
- return {}
-
- def new_resource(self, type_, name, inputs, provider, id_):
- if type_ == 'aws:ec2/securityGroup:SecurityGroup':
- state = {
- 'arn': 'arn:aws:ec2:us-west-2:123456789012:security-group/sg-12345678',
- 'name': inputs['name'] if 'name' in inputs else name + '-sg',
- }
- return ['sg-12345678', dict(inputs, **state)]
- elif type_ == 'aws:ec2/instance:Instance':
- state = {
- 'arn': 'arn:aws:ec2:us-west-2:123456789012:instance/i-1234567890abcdef0',
- 'instanceState': 'running',
- 'primaryNetworkInterfaceId': 'eni-12345678',
- 'privateDns': 'ip-10-0-1-17.ec2.internal',
- 'public_dns': 'ec2-203-0-113-12.compute-1.amazonaws.com',
- 'public_ip': '203.0.113.12',
- }
- return ['i-1234567890abcdef0', dict(inputs, **state)]
- else:
- return ['', {}]
-
-pulumi.runtime.set_mocks(MyMocks())
-
-# Now actually import the code that creates resources, and then test it.
-import resources
-
-class TestingWithMocks(unittest.TestCase):
- @pulumi.runtime.test
- def test_component(self):
- def check_outprop(outprop):
- self.assertEqual(outprop, 'output: hello')
- return resources.mycomponent.outprop.apply(check_outprop)
-
- @pulumi.runtime.test
- def test_custom(self):
- def check_ip(ip):
- self.assertEqual(ip, '203.0.113.12')
- return resources.myinstance.public_ip.apply(check_ip)

MODIFY
testing-unit-ts/README.md
testing-unit-ts/README.md
@@ -18,11 +18,7 @@ An example of writing mock-based unit tests with both infrastructure definition
2. Run the tests:
```
- $ PULUMI_TEST_MODE=true \
- PULUMI_NODEJS_STACK="my-ws" \
- PULUMI_NODEJS_PROJECT="dev" \
- PULUMI_CONFIG='{ "aws:region": "us-west-2" }' \
- mocha -r ts-node/register ec2tests.ts
+ $ mocha -r ts-node/register ec2tests.ts
Infrastructure
#server

MODIFY
testing-unit-ts/index.ts
testing-unit-ts/index.ts
import * as aws from "@pulumi/aws";
-export let group = new aws.ec2.SecurityGroup("web-secgrp", {
+export const group = new aws.ec2.SecurityGroup("web-secgrp", {
ingress: [
// uncomment to fail a test:
// { protocol: "tcp", fromPort: 22, toPort: 22, cidrBlocks: ["0.0.0.0/0"] },
@@ -10,11 +10,11 @@ export let group = new aws.ec2.SecurityGroup("web-secgrp", {
],
});
-export let server = new aws.ec2.Instance("web-server-www", {
+export const server = new aws.ec2.Instance("web-server-www", {
instanceType: "t2.micro",
securityGroups: [ group.name ], // reference the group object above
ami: "ami-c55673a0", // AMI for us-east-2 (Ohio),
- // remove or rename to fail a test:
+ // comment to fail a test:
tags: { Name: "www-server" }, // name tag
// uncomment to fail a test:
// userData: `#!/bin/bash echo "Hello, World!" > index.html nohup python -m SimpleHTTPServer 80 &`,

Unify unit tests for the guide (#615)