diff --git a/packages/secrets-manager/test/ConfigProvider.test.ts b/packages/secrets-manager/test/ConfigProvider.test.ts index a033721..350fb79 100644 --- a/packages/secrets-manager/test/ConfigProvider.test.ts +++ b/packages/secrets-manager/test/ConfigProvider.test.ts @@ -1,24 +1,23 @@ import { - GetSecretValueCommandOutput, + InvalidRequestException, ResourceNotFoundException, - SecretsManagerClient, } from "@aws-sdk/client-secrets-manager"; import { BaseSecretsManagerServiceLayer, SecretsManagerClientInstance, } from "@effect-aws/client-secrets-manager"; -import { Arg, Substitute } from "@fluffy-spoon/substitute"; +import { Arg } from "@fluffy-spoon/substitute"; import { Config, ConfigError, Effect, Exit, Layer, Secret } from "effect"; import { describe, expect, it } from "vitest"; +import { SubstituteBuilder } from "./utils"; import { fromSecretsManager } from "../src/ConfigProvider"; describe("fromSecretsManager", () => { it("should load configuration from AWS Secrets Manager", async () => { - const clientSubstitute = Substitute.for(); - const commandOutputSubstitute = - Substitute.for(); - commandOutputSubstitute.SecretString?.returns?.("mocked-secret"); - clientSubstitute.send(Arg.all()).resolves(commandOutputSubstitute); + const clientSubstitute = SubstituteBuilder.forSecretsManager() + .mockGetSecretValue() + .withSecretString("mocked-secret") + .succeeds(); const clientInstanceLayer = Layer.succeed( SecretsManagerClientInstance, @@ -39,13 +38,14 @@ describe("fromSecretsManager", () => { }); it("should load default value if the secret does not exist", async () => { - const clientSubstitute = Substitute.for(); - clientSubstitute.send(Arg.all()).rejects( - new ResourceNotFoundException({ - $metadata: {}, - message: "mocked-error", - }), - ); + const clientSubstitute = SubstituteBuilder.forSecretsManager() + .mockGetSecretValue() + .failsWith( + new ResourceNotFoundException({ + $metadata: {}, + message: "mocked-error", + }), + ); const clientInstanceLayer = Layer.succeed( SecretsManagerClientInstance, @@ -67,15 +67,53 @@ describe("fromSecretsManager", () => { clientSubstitute.received(1).send(Arg.any(), {}); }); - it("should fail if the secret does not exist", async () => { - const clientSubstitute = Substitute.for(); - clientSubstitute.send(Arg.all()).rejects( - new ResourceNotFoundException({ - $metadata: {}, - message: "mocked-error", - }), + it("should fail if request is invalid", async () => { + const clientSubstitute = SubstituteBuilder.forSecretsManager() + .mockGetSecretValue() + .failsWith( + new InvalidRequestException({ + $metadata: {}, + message: "mocked-error", + }), + ); + + const clientInstanceLayer = Layer.succeed( + SecretsManagerClientInstance, + clientSubstitute, + ); + const serviceLayer = Layer.provide( + BaseSecretsManagerServiceLayer, + clientInstanceLayer, ); + const result = await Config.secret("test").pipe( + Config.withDefault(Secret.fromString("mocked-default-value")), + Effect.withConfigProvider(fromSecretsManager({ serviceLayer })), + Effect.map(Secret.value), + Effect.runPromiseExit, + ); + + expect(result).toEqual( + Exit.fail( + ConfigError.InvalidData( + ["test"], + "Invalid request to AWS Secrets Manager", + ), + ), + ); + clientSubstitute.received(1).send(Arg.any(), {}); + }); + + it("should fail if the secret does not exist", async () => { + const clientSubstitute = SubstituteBuilder.forSecretsManager() + .mockGetSecretValue() + .failsWith( + new ResourceNotFoundException({ + $metadata: {}, + message: "mocked-error", + }), + ); + const clientInstanceLayer = Layer.succeed( SecretsManagerClientInstance, clientSubstitute, @@ -102,11 +140,9 @@ describe("fromSecretsManager", () => { }); it("should fail if the secret is empty", async () => { - const clientSubstitute = Substitute.for(); - const commandOutputSubstitute = - Substitute.for(); - commandOutputSubstitute.SecretString?.returns?.(undefined); - clientSubstitute.send(Arg.all()).resolves(commandOutputSubstitute); + const clientSubstitute = SubstituteBuilder.forSecretsManager() + .mockGetSecretValue() + .succeeds(); const clientInstanceLayer = Layer.succeed( SecretsManagerClientInstance, diff --git a/packages/secrets-manager/test/utils/index.ts b/packages/secrets-manager/test/utils/index.ts new file mode 100644 index 0000000..eaaaa6f --- /dev/null +++ b/packages/secrets-manager/test/utils/index.ts @@ -0,0 +1 @@ +export * from "./substituteBuilder"; diff --git a/packages/secrets-manager/test/utils/substituteBuilder.ts b/packages/secrets-manager/test/utils/substituteBuilder.ts new file mode 100644 index 0000000..2ebe697 --- /dev/null +++ b/packages/secrets-manager/test/utils/substituteBuilder.ts @@ -0,0 +1,50 @@ +import { + GetSecretValueCommandOutput, + SecretsManagerClient, +} from "@aws-sdk/client-secrets-manager"; +import Substitute, { Arg, SubstituteOf } from "@fluffy-spoon/substitute"; + +export class SubstituteBuilder { + static forSecretsManager(): SecretsManagerSubstituteBuilder { + return new SecretsManagerSubstituteBuilder(); + } +} + +class SecretsManagerSubstituteBuilder { + public substitute: SubstituteOf; + + constructor() { + this.substitute = Substitute.for(); + } + + mockGetSecretValue() { + return new GetSecretValueSubstituteBuilder(this.substitute); + } +} + +class GetSecretValueSubstituteBuilder { + private secretString?: string; + + constructor( + private readonly substitute: SubstituteOf, + ) {} + + withSecretString(secretString: string) { + this.secretString = secretString; + return this; + } + + failsWith(error: Error) { + this.substitute.send(Arg.all()).rejects(error); + return this.substitute; + } + + succeeds() { + const substituteResponse = Substitute.for(); + substituteResponse.SecretString?.returns?.(this.secretString); + + this.substitute.send(Arg.all()).resolves(substituteResponse); + + return this.substitute; + } +}