You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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" }
      }
    }
  ]
}

问题原因分析

  1. Mock对象类型不匹配:直接将pulumi.output()强制转换为aws.iam.OpenIdConnectProvider,但该资源的arn和url属性本身是pulumi.Output<string>类型,而非普通字符串。强制转换后,测试中直接访问mockOidcProvider.arn会获取到Output对象的底层引用,而非实际字符串值,导致Policy生成时无法正确解析。
  2. 字符串拼接时机错误:在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 17:07:44