Skip to content

Commit

Permalink
feat(api): adminDeleteUserAttributes full support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Dec 11, 2021
1 parent 308c9c2 commit 1a47086
Show file tree
Hide file tree
Showing 6 changed files with 163 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| AdminConfirmSignUp ||
| AdminCreateUser | 🕒 (partial support) |
| AdminDeleteUser ||
| AdminDeleteUserAttributes | |
| AdminDeleteUserAttributes | |
| AdminDisableProviderForUser ||
| AdminDisableUser ||
| AdminEnableUser ||
Expand Down
56 changes: 56 additions & 0 deletions integration-tests/aws-sdk/adminDeleteUserAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { UUID } from "../../src/__tests__/patterns";
import { withCognitoSdk } from "./setup";

describe(
"CognitoIdentityServiceProvider.adminDeleteUserAttributes",
withCognitoSdk((Cognito) => {
it("updates a user's attributes", async () => {
const client = Cognito();

await client
.adminCreateUser({
UserAttributes: [
{ Name: "email", Value: "[email protected]" },
{ Name: "custom:example", Value: "1" },
],
Username: "abc",
UserPoolId: "test",
DesiredDeliveryMediums: ["EMAIL"],
})
.promise();

let user = await client
.adminGetUser({
UserPoolId: "test",
Username: "abc",
})
.promise();

expect(user.UserAttributes).toEqual([
{ Name: "sub", Value: expect.stringMatching(UUID) },
{ Name: "email", Value: "[email protected]" },
{ Name: "custom:example", Value: "1" },
]);

await client
.adminDeleteUserAttributes({
UserPoolId: "test",
Username: "abc",
UserAttributeNames: ["custom:example"],
})
.promise();

user = await client
.adminGetUser({
UserPoolId: "test",
Username: "abc",
})
.promise();

expect(user.UserAttributes).toEqual([
{ Name: "sub", Value: expect.stringMatching(UUID) },
{ Name: "email", Value: "[email protected]" },
]);
});
})
);
6 changes: 6 additions & 0 deletions src/services/userPoolService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ export const attributesAppend = (
return attributesFromRecord(attributeSet);
};

export const attributesRemove = (
attributes: AttributeListType | undefined,
...toRemove: readonly string[]
): AttributeListType =>
attributes?.filter((x) => !toRemove.includes(x.Name)) ?? [];

export const customAttributes = (
attributes: AttributeListType | undefined
): AttributeListType =>
Expand Down
60 changes: 60 additions & 0 deletions src/targets/adminDeleteUserAttributes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ClockFake } from "../__tests__/clockFake";
import { newMockCognitoService } from "../__tests__/mockCognitoService";
import { newMockUserPoolService } from "../__tests__/mockUserPoolService";
import { TestContext } from "../__tests__/testContext";
import { NotAuthorizedError } from "../errors";
import { UserPoolService } from "../services";
import { attribute } from "../services/userPoolService";
import {
AdminDeleteUserAttributes,
AdminDeleteUserAttributesTarget,
} from "./adminDeleteUserAttributes";
import * as TDB from "../__tests__/testDataBuilder";

describe("AdminDeleteUserAttributes target", () => {
let adminDeleteUserAttributes: AdminDeleteUserAttributesTarget;
let mockUserPoolService: jest.Mocked<UserPoolService>;
let clock: ClockFake;

beforeEach(() => {
mockUserPoolService = newMockUserPoolService();
clock = new ClockFake(new Date());
adminDeleteUserAttributes = AdminDeleteUserAttributes({
clock,
cognito: newMockCognitoService(mockUserPoolService),
});
});

it("throws if the user doesn't exist", async () => {
await expect(
adminDeleteUserAttributes(TestContext, {
UserPoolId: "test",
Username: "abc",
UserAttributeNames: ["custom:example"],
})
).rejects.toEqual(new NotAuthorizedError());
});

it("saves the updated attributes on the user", async () => {
const user = TDB.user({
Attributes: [
attribute("email", "[email protected]"),
attribute("custom:example", "1"),
],
});

mockUserPoolService.getUserByUsername.mockResolvedValue(user);

await adminDeleteUserAttributes(TestContext, {
UserPoolId: "test",
Username: "abc",
UserAttributeNames: ["custom:example"],
});

expect(mockUserPoolService.saveUser).toHaveBeenCalledWith(TestContext, {
...user,
Attributes: [attribute("email", "[email protected]")],
UserLastModifiedDate: clock.get(),
});
});
});
38 changes: 38 additions & 0 deletions src/targets/adminDeleteUserAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
AdminDeleteUserAttributesRequest,
AdminDeleteUserAttributesResponse,
} from "aws-sdk/clients/cognitoidentityserviceprovider";
import { NotAuthorizedError } from "../errors";
import { Services } from "../services";
import { attributesRemove } from "../services/userPoolService";
import { Target } from "./router";

export type AdminDeleteUserAttributesTarget = Target<
AdminDeleteUserAttributesRequest,
AdminDeleteUserAttributesResponse
>;

type AdminDeleteUserAttributesServices = Pick<Services, "clock" | "cognito">;

export const AdminDeleteUserAttributes =
({
clock,
cognito,
}: AdminDeleteUserAttributesServices): AdminDeleteUserAttributesTarget =>
async (ctx, req) => {
const userPool = await cognito.getUserPool(ctx, req.UserPoolId);
const user = await userPool.getUserByUsername(ctx, req.Username);
if (!user) {
throw new NotAuthorizedError();
}

const updatedUser = {
...user,
Attributes: attributesRemove(user.Attributes, ...req.UserAttributeNames),
UserLastModifiedDate: clock.get(),
};

await userPool.saveUser(ctx, updatedUser);

return {};
};
2 changes: 2 additions & 0 deletions src/targets/router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Logger } from "../log";
import { Services } from "../services";
import { UnsupportedError } from "../errors";
import { AdminDeleteUserAttributes } from "./adminDeleteUserAttributes";
import { AdminSetUserPassword } from "./adminSetUserPassword";
import { ConfirmForgotPassword } from "./confirmForgotPassword";
import { ConfirmSignUp } from "./confirmSignUp";
Expand Down Expand Up @@ -33,6 +34,7 @@ export const Targets = {
AdminConfirmSignUp,
AdminCreateUser,
AdminDeleteUser,
AdminDeleteUserAttributes,
AdminGetUser,
AdminInitiateAuth,
AdminSetUserPassword,
Expand Down

0 comments on commit 1a47086

Please sign in to comment.