如何在Go单元测试中正确Mock Pulumi AWS OIDC资源?
问题:Pulumi单元测试Mock OIDC提供者后AssumeRolePolicy生成异常
问题描述
- 自定义函数接收
aws.iam.OpenIdConnectProvider资源作为入参,生成包含该OIDC信息的AssumeRolePolicy,进而创建IAM角色 - 单元测试中Mock该OIDC提供者资源后,生成的Policy出现两个异常:
- OIDC提供者的ARN字段为空
- 处理后的URL仅保留
:sub后缀,缺失完整OIDC地址
相关代码示例
业务函数代码
import * as aws from "@pulumi/aws"; export function createOidcRole(oidcProvider: aws.iam.OpenIdConnectProvider, roleName: string) { const assumeRolePolicy = aws.iam.getPolicyDocument({ statements: [{ actions: ["sts:AssumeRoleWithWebIdentity"], principals: [{ type: "Federated", identifiers: [oidcProvider.arn], }], conditions: [{ test: "StringEquals", values: ["system:serviceaccount:kube-system:aws-load-balancer-controller"], variable: `${oidcProvider.url}:sub`, }], }], }); return new aws.iam.Role(roleName, { assumeRolePolicy: assumeRolePolicy.then(p => p.json), name: roleName, }); }
单元测试代码(问题版本)
import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; import { createOidcRole } from "./index"; describe("createOidcRole", () => { it("should generate correct AssumeRolePolicy with mocked OIDC provider", async () => { // Mock OIDC Provider const mockOidcProvider = pulumi.output({ arn: "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/ABCDEFGHIJKLMNOPQRSTUVWXYZ", url: "https://oidc.eks.us-west-2.amazonaws.com/id/ABCDEFGHIJKLMNOPQRSTUVWXYZ", }) as unknown as aws.iam.OpenIdConnectProvider; const role = createOidcRole(mockOidcProvider, "test-oidc-role"); // 获取生成的Policy内容 const policyJson = await pulumi.output(role.assumeRolePolicy).apply(JSON.parse); // 断言验证 expect(policyJson.Statement[0].Principal.Federated).toBe(mockOidcProvider.arn); expect(policyJson.Statement[0].Condition.StringEquals[`${mockOidcProvider.url}:sub`]).toBeDefined(); }); });
测试差异结果
实际生成的Policy片段:
{ "Statement": [ { "Action": "sts:AssumeRoleWithWebIdentity", "Principal": { "Federated": "" }, "Condition": { "StringEquals": { ":sub": "system:serviceaccount:kube-system:aws-load-balancer-controller" } } } ] }
问题原因分析
- Mock对象类型不匹配:直接将
pulumi.output()强制转换为aws.iam.OpenIdConnectProvider,但该资源的arn和url属性本身是pulumi.Output<string>类型,而非普通字符串。强制转换后,测试中直接访问mockOidcProvider.arn会获取到Output对象的底层引用,而非实际字符串值,导致Policy生成时无法正确解析。 - 字符串拼接时机错误:在
getPolicyDocument中直接使用${oidcProvider.url}:sub进行字符串拼接,此时oidcProvider.url是Output对象,无法直接参与字符串拼接,会被转换为默认的对象字符串表示,最终仅保留:sub。
解决方案
修正单元测试的Mock方式
正确模拟aws.iam.OpenIdConnectProvider的结构,确保其属性为pulumi.Output<string>类型:
import * as pulumi from "@pulumi/pulumi"; import * as aws from "@pulumi/aws"; import { createOidcRole } from "./index"; describe("createOidcRole", () => { it("should generate correct AssumeRolePolicy with mocked OIDC provider", async () => { // 正确Mock OIDC Provider,属性为Output类型 const mockOidcProvider = { arn: pulumi.output("arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/ABCDEFGHIJKLMNOPQRSTUVWXYZ"), url: pulumi.output("https://oidc.eks.us-west-2.amazonaws.com/id/ABCDEFGHIJKLMNOPQRSTUVWXYZ"), } as unknown as aws.iam.OpenIdConnectProvider; const role = createOidcRole(mockOidcProvider, "test-oidc-role"); // 通过Output的apply获取实际Policy内容并断言 await pulumi.output(role.assumeRolePolicy).apply(policyJson => { const policy = JSON.parse(policyJson); expect(policy.Statement[0].Principal.Federated).toBe(mockOidcProvider.arn.get()); expect(policy.Statement[0].Condition.StringEquals[`${mockOidcProvider.url.get()}:sub`]).toBe("system:serviceaccount:kube-system:aws-load-balancer-controller"); return Promise.resolve(); }); }); });
修正业务函数的字符串拼接逻辑
在getPolicyDocument中,使用pulumi.interpolate处理Output对象的字符串拼接:
import * as aws from "@pulumi/aws"; import * as pulumi from "@pulumi/pulumi"; export function createOidcRole(oidcProvider: aws.iam.OpenIdConnectProvider, roleName: string) { const assumeRolePolicy = aws.iam.getPolicyDocument({ statements: [{ actions: ["sts:AssumeRoleWithWebIdentity"], principals: [{ type: "Federated", identifiers: [oidcProvider.arn], }], conditions: [{ test: "StringEquals", values: ["system:serviceaccount:kube-system:aws-load-balancer-controller"], // 使用pulumi.interpolate处理Output字符串拼接 variable: pulumi.interpolate`${oidcProvider.url}:sub`, }], }], }); return new aws.iam.Role(roleName, { assumeRolePolicy: assumeRolePolicy.then(p => p.json), name: roleName, }); }
关键注意点
- Pulumi资源的属性均为
Output类型,不能直接当作普通字符串使用,必须通过.apply()或pulumi.interpolate处理后才能获取实际值或进行字符串操作。 - 单元测试中Mock资源时,要确保Mock对象的属性结构与真实资源一致,即属性为
Output类型,而非直接的字符串。 - 验证测试结果时,需通过
Output的.apply()方法获取实际值后再进行断言,避免直接比较Output对象。
内容的提问来源于stack exchange,提问作者GoldenOats
相关产品推荐
相关产品推荐

