Skip to content

Commit

Permalink
feat(api): adminDeleteUser full support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Nov 25, 2021
1 parent 6a75fea commit 58f33e2
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 149 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| AdminAddUserToGroup ||
| AdminConfirmSignUp | 🕒 (partial support) |
| AdminCreateUser | 🕒 (partial support) |
| AdminDeleteUser | 🕒 (partial support) |
| AdminDeleteUser | |
| AdminDeleteUserAttributes ||
| AdminDisableProviderForUser ||
| AdminDisableUser ||
Expand Down
70 changes: 70 additions & 0 deletions integration-tests/aws-sdk/adminDeleteUser.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ClockFake } from "../../src/__tests__/clockFake";
import { UUID } from "../../src/__tests__/patterns";
import { UserNotFoundError } from "../../src/errors";
import { withCognitoSdk } from "./setup";

const currentDate = new Date();
const roundedDate = new Date(currentDate.getTime());
roundedDate.setMilliseconds(0);

const clock = new ClockFake(currentDate);

describe(
"CognitoIdentityServiceProvider.adminDeleteUser",
withCognitoSdk(
(Cognito) => {
it("deletes a user", async () => {
const client = Cognito();

// create the user
const createUserResult = await client
.adminCreateUser({
Username: "abc",
UserPoolId: "test",

// TODO: shouldn't need to supply this
TemporaryPassword: "TemporaryPassword",
})
.promise();

// verify they exist
const beforeUserResult = await client
.adminGetUser({
Username: "abc",
UserPoolId: "test",
})
.promise();

expect(beforeUserResult).toEqual({
Enabled: true,
UserAttributes: createUserResult.User?.Attributes,
UserCreateDate: createUserResult.User?.UserCreateDate,
UserLastModifiedDate: createUserResult.User?.UserLastModifiedDate,
Username: createUserResult.User?.Username,
UserStatus: createUserResult.User?.UserStatus,
});

// delete the user
await client
.adminDeleteUser({
Username: "abc",
UserPoolId: "test",
})
.promise();

// verify they don't exist anymore
await expect(
client
.adminGetUser({
Username: "abc",
UserPoolId: "test",
})
.promise()
).rejects.toEqual(new UserNotFoundError("User does not exist"));
});
},
{
clock,
}
)
);
65 changes: 65 additions & 0 deletions integration-tests/dataStore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,71 @@ describe("Data Store", () => {
});
});

describe("delete", () => {
it("deletes a value", async () => {
const dataStore = await createDataStore("example", {}, path);

await dataStore.set("key1", 1);
await dataStore.set("key2", 2);

const fileBefore = JSON.parse(
await readFile(path + "/example.json", "utf-8")
);

expect(fileBefore).toEqual({
key1: 1,
key2: 2,
});

await dataStore.delete("key1");

const fileAfter = JSON.parse(
await readFile(path + "/example.json", "utf-8")
);

expect(fileAfter).toEqual({
key2: 2,
});
});

it("deletes a nested value using array syntax", async () => {
const dataStore = await createDataStore("example", {}, path);

await dataStore.set(["key", "a", "b"], 1);
await dataStore.set(["key", "a", "c"], 2);
await dataStore.set("key2", 3);

const fileBefore = JSON.parse(
await readFile(path + "/example.json", "utf-8")
);

expect(fileBefore).toEqual({
key: {
a: {
b: 1,
c: 2,
},
},
key2: 3,
});

await dataStore.delete(["key", "a", "b"]);

const fileAfter = JSON.parse(
await readFile(path + "/example.json", "utf-8")
);

expect(fileAfter).toEqual({
key: {
a: {
c: 2,
},
},
key2: 3,
});
});
});

describe("set", () => {
it("saves a value", async () => {
const dataStore = await createDataStore("example", {}, path);
Expand Down
8 changes: 8 additions & 0 deletions src/__tests__/mockDataStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DataStore } from "../services/dataStore";

export const newMockDataStore = (): jest.Mocked<DataStore> => ({
delete: jest.fn(),
get: jest.fn(),
getRoot: jest.fn(),
set: jest.fn(),
});
5 changes: 3 additions & 2 deletions src/__tests__/mockUserPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ export const MockUserPoolClient = {
createAppClient: jest.fn() as jest.MockedFunction<
UserPoolClient["createAppClient"]
>,
deleteUser: jest.fn() as jest.MockedFunction<UserPoolClient["deleteUser"]>,
getUserByUsername: jest.fn() as jest.MockedFunction<
UserPoolClient["getUserByUsername"]
>,
listUsers: jest.fn() as jest.MockedFunction<UserPoolClient["listUsers"]>,
saveUser: jest.fn() as jest.MockedFunction<UserPoolClient["saveUser"]>,
listGroups: jest.fn() as jest.MockedFunction<UserPoolClient["listGroups"]>,
listUsers: jest.fn() as jest.MockedFunction<UserPoolClient["listUsers"]>,
saveGroup: jest.fn() as jest.MockedFunction<UserPoolClient["saveGroup"]>,
saveUser: jest.fn() as jest.MockedFunction<UserPoolClient["saveUser"]>,
};
16 changes: 8 additions & 8 deletions src/__tests__/testDataBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { User } from "../services/userPoolClient";

export const user = (): User => ({
Attributes: [],
Enabled: true,
Password: "Password123!",
UserCreateDate: new Date().getTime(),
UserLastModifiedDate: new Date().getTime(),
Username: "Username",
UserStatus: "CONFIRMED",
export const user = (partial?: Partial<User>): User => ({
Attributes: partial?.Attributes ?? [],
Enabled: partial?.Enabled ?? true,
Password: partial?.Password ?? "Password123!",
UserCreateDate: partial?.UserCreateDate ?? new Date().getTime(),
UserLastModifiedDate: partial?.UserLastModifiedDate ?? new Date().getTime(),
Username: partial?.Username ?? "Username",
UserStatus: partial?.UserStatus ?? "CONFIRMED",
});
4 changes: 2 additions & 2 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export class NotAuthorizedError extends CognitoError {
}

export class UserNotFoundError extends CognitoError {
public constructor() {
super("UserNotFoundException", "User not found");
public constructor(message = "User not found") {
super("UserNotFoundException", message);
}
}

Expand Down
7 changes: 2 additions & 5 deletions src/services/cognitoClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { newMockDataStore } from "../__tests__/mockDataStore";
import { MockLogger } from "../__tests__/mockLogger";
import { ResourceNotFoundError } from "../errors";
import { DateClock } from "./clock";
Expand All @@ -13,11 +14,7 @@ describe("Cognito Client", () => {
const clock = new DateClock();

beforeEach(() => {
mockDataStore = {
set: jest.fn(),
get: jest.fn(),
getRoot: jest.fn(),
};
mockDataStore = newMockDataStore();
createUserPoolClient = jest.fn().mockResolvedValue(mockUserPool);
createDataStore = jest.fn().mockResolvedValue(mockDataStore);
});
Expand Down
10 changes: 9 additions & 1 deletion src/services/dataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { promisify } from "util";
const mkdir = promisify(fs.mkdir);

export interface DataStore {
getRoot<T>(): Promise<T | null>;
delete(key: string | string[]): Promise<void>;
get<T>(key: string | string[]): Promise<T | null>;
get<T>(key: string | string[], defaultValue: T): Promise<T>;
getRoot<T>(): Promise<T | null>;
set<T>(key: string | string[], value: T): Promise<void>;
}

Expand All @@ -32,6 +33,13 @@ export const createDataStore: CreateDataStore = async (
db.default(defaults);

return {
async delete(key: string | string[]) {
(key instanceof Array ? key : [key])
.reduce((acc, k) => acc.get(k), db)
.delete();
await db.save();
},

async getRoot() {
return (await db.value()) ?? null;
},
Expand Down
Loading

0 comments on commit 58f33e2

Please sign in to comment.