Skip to content

Commit

Permalink
test: implement substitute builder for secrets manager
Browse files Browse the repository at this point in the history
  • Loading branch information
floydspace committed Sep 7, 2024
1 parent 80245a5 commit 70a5f87
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 27 deletions.
90 changes: 63 additions & 27 deletions packages/secrets-manager/test/ConfigProvider.test.ts
Original file line number Diff line number Diff line change
@@ -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<SecretsManagerClient>();
const commandOutputSubstitute =
Substitute.for<GetSecretValueCommandOutput>();
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,
Expand All @@ -39,13 +38,14 @@ describe("fromSecretsManager", () => {
});

it("should load default value if the secret does not exist", async () => {
const clientSubstitute = Substitute.for<SecretsManagerClient>();
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,
Expand All @@ -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<SecretsManagerClient>();
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,
Expand All @@ -102,11 +140,9 @@ describe("fromSecretsManager", () => {
});

it("should fail if the secret is empty", async () => {
const clientSubstitute = Substitute.for<SecretsManagerClient>();
const commandOutputSubstitute =
Substitute.for<GetSecretValueCommandOutput>();
commandOutputSubstitute.SecretString?.returns?.(undefined);
clientSubstitute.send(Arg.all()).resolves(commandOutputSubstitute);
const clientSubstitute = SubstituteBuilder.forSecretsManager()
.mockGetSecretValue()
.succeeds();

const clientInstanceLayer = Layer.succeed(
SecretsManagerClientInstance,
Expand Down
1 change: 1 addition & 0 deletions packages/secrets-manager/test/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./substituteBuilder";
50 changes: 50 additions & 0 deletions packages/secrets-manager/test/utils/substituteBuilder.ts
Original file line number Diff line number Diff line change
@@ -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<SecretsManagerClient>;

constructor() {
this.substitute = Substitute.for<SecretsManagerClient>();
}

mockGetSecretValue() {
return new GetSecretValueSubstituteBuilder(this.substitute);
}
}

class GetSecretValueSubstituteBuilder {
private secretString?: string;

constructor(
private readonly substitute: SubstituteOf<SecretsManagerClient>,
) {}

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<GetSecretValueCommandOutput>();
substituteResponse.SecretString?.returns?.(this.secretString);

this.substitute.send(Arg.all()).resolves(substituteResponse);

return this.substitute;
}
}

0 comments on commit 70a5f87

Please sign in to comment.