Skip to content

Commit

Permalink
feat(api): listUserPools full support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Nov 25, 2021
1 parent 86c2a8a commit d2e5324
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 5 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,14 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| GlobalSignOut ||
| InitiateAuth | 🕒 (partial support) |
| ListDevices ||
| ListGroups | 🕒 (partial support) |
| ListGroups | ✅¹ |
| ListIdentityProviders ||
| ListResourceServers ||
| ListTagsForResource ||
| ListUserImportJobs ||
| ListUserPoolClients ||
| ListUserPools | |
| ListUsers | 🕒 (partial support) |
| ListUserPools | ✅¹ |
| ListUsers | ✅¹ |
| ListUsersInGroup ||
| ResendConfirmationCode ||
| RespondToAuthChallenge | 🕒 (partial support) |
Expand All @@ -130,6 +130,8 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
| VerifySoftwareToken ||
| VerifyUserAttribute ||

> ¹ does not support pagination or query filters, all results and attributes will be returned in the first request.
Additional supported features:

- JWKs verification
Expand Down
44 changes: 44 additions & 0 deletions integration-tests/aws-sdk/listUserPools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { withCognitoSdk } from "./setup";

describe(
"CognitoIdentityServiceProvider.listUserPools",
withCognitoSdk((Cognito) => {
it("lists user pools", async () => {
const client = Cognito();

// TODO: refactor this when we support createUserPool
// right now we're relying on pools being created on-demand when a user is created
await client
.adminCreateUser({
Username: "abc",
UserPoolId: "test-1",
TemporaryPassword: "TemporaryPassword", // TODO: shouldn't need to supply this
})
.promise();
await client
.adminCreateUser({
Username: "abc",
UserPoolId: "test-2",
TemporaryPassword: "TemporaryPassword", // TODO: shouldn't need to supply this
})
.promise();
await client
.adminCreateUser({
Username: "abc",
UserPoolId: "test-3",
TemporaryPassword: "TemporaryPassword", // TODO: shouldn't need to supply this
})
.promise();

const result = await client
.listUserPools({
MaxResults: 10,
})
.promise();

expect(result).toEqual({
UserPools: [{ Id: "test-1" }, { Id: "test-2" }, { Id: "test-3" }],
});
});
})
);
47 changes: 47 additions & 0 deletions integration-tests/cognitoService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,51 @@ describe("Cognito Service", () => {

expect(fs.existsSync(`${dataDirectory}/clients.json`)).toBe(true);
});

it("creates a user pool database", async () => {
const cognitoService = await CognitoServiceImpl.create(
dataDirectory,
{
Id: "local",
UsernameAttributes: [],
},
new DateClock(),
createDataStore,
UserPoolServiceImpl.create,
MockLogger
);

await cognitoService.getUserPool("test-pool");

expect(fs.existsSync(`${dataDirectory}/test-pool.json`)).toBe(true);
});

it("lists multiple user pools", async () => {
const cognitoService = await CognitoServiceImpl.create(
dataDirectory,
{
Id: "local",
UsernameAttributes: [],
},
new DateClock(),
createDataStore,
UserPoolServiceImpl.create,
MockLogger
);

await cognitoService.getUserPool("test-pool-1");
await cognitoService.getUserPool("test-pool-2");
await cognitoService.getUserPool("test-pool-3");

expect(fs.existsSync(`${dataDirectory}/test-pool-1.json`)).toBe(true);
expect(fs.existsSync(`${dataDirectory}/test-pool-2.json`)).toBe(true);
expect(fs.existsSync(`${dataDirectory}/test-pool-3.json`)).toBe(true);

const pools = await cognitoService.listUserPools();
expect(pools).toEqual([
{ Id: "test-pool-1", UsernameAttributes: [] },
{ Id: "test-pool-2", UsernameAttributes: [] },
{ Id: "test-pool-3", UsernameAttributes: [] },
]);
});
});
1 change: 1 addition & 0 deletions src/__tests__/mockCognitoService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export const newMockCognitoService = (
getAppClient: jest.fn(),
getUserPool: jest.fn().mockResolvedValue(userPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(userPoolClient),
listUserPools: jest.fn(),
});
8 changes: 7 additions & 1 deletion src/__tests__/testDataBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { v4 } from "uuid";
import { Group, User } from "../services/userPoolService";
import { Group, User, UserPool } from "../services/userPoolService";

export const id = (prefix: string, number?: number) =>
`${prefix}${number ?? Math.floor(Math.random() * 100000)}`;
Expand Down Expand Up @@ -28,3 +28,9 @@ export const user = (partial?: Partial<User>): User => ({
Username: partial?.Username ?? id("User"),
UserStatus: partial?.UserStatus ?? "CONFIRMED",
});

export const userPool = (partial?: Partial<UserPool>): UserPool => ({
Id: partial?.Id ?? id("UserPool"),
MfaConfiguration: partial?.MfaConfiguration ?? undefined,
UsernameAttributes: partial?.UsernameAttributes ?? undefined,
});
32 changes: 31 additions & 1 deletion src/services/cognitoService.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as path from "path";
import { ResourceNotFoundError } from "../errors";
import { Logger } from "../log";
import { AppClient } from "./appClient";
Expand All @@ -8,11 +9,18 @@ import {
UserPool,
UserPoolService,
} from "./userPoolService";
import fs from "fs";
import { promisify } from "util";

const readdir = promisify(fs.readdir);

const CLIENTS_DATABASE_NAME = "clients";

export interface CognitoService {
getAppClient(clientId: string): Promise<AppClient | null>;
getUserPool(userPoolId: string): Promise<UserPoolService>;
getUserPoolForClientId(clientId: string): Promise<UserPoolService>;
listUserPools(): Promise<readonly UserPool[]>;
}

type UserPoolDefaultConfig = Omit<UserPool, "Id">;
Expand All @@ -35,7 +43,7 @@ export class CognitoServiceImpl implements CognitoService {
logger: Logger
): Promise<CognitoService> {
const clients = await createDataStore(
"clients",
CLIENTS_DATABASE_NAME,
{ Clients: {} },
dataDirectory
);
Expand Down Expand Up @@ -101,4 +109,26 @@ export class CognitoServiceImpl implements CognitoService {
public async getAppClient(clientId: string): Promise<AppClient | null> {
return this.clients.get(["Clients", clientId]);
}

public async listUserPools(): Promise<readonly UserPool[]> {
const entries = await readdir(this.dataDirectory, { withFileTypes: true });

return Promise.all(
entries
.filter(
(x) =>
x.isFile() &&
path.extname(x.name) === ".json" &&
path.basename(x.name, path.extname(x.name)) !==
CLIENTS_DATABASE_NAME
)
.map(async (x) => {
const userPool = await this.getUserPool(
path.basename(x.name, path.extname(x.name))
);

return userPool.config;
})
);
}
}
38 changes: 38 additions & 0 deletions src/targets/listUserPools.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { newMockCognitoService } from "../__tests__/mockCognitoService";
import { newMockUserPoolService } from "../__tests__/mockUserPoolService";
import * as TDB from "../__tests__/testDataBuilder";
import { CognitoService } from "../services";
import { ListUserPools, ListUserPoolsTarget } from "./listUserPools";

describe("ListUserPools target", () => {
let listUserPools: ListUserPoolsTarget;
let mockCognitoService: jest.Mocked<CognitoService>;

beforeEach(() => {
mockCognitoService = newMockCognitoService(newMockUserPoolService());
listUserPools = ListUserPools({
cognito: mockCognitoService,
});
});

it("lists user pools", async () => {
const userPool1 = TDB.userPool();
const userPool2 = TDB.userPool();

mockCognitoService.listUserPools.mockResolvedValue([userPool1, userPool2]);

const output = await listUserPools({
MaxResults: 10,
});

expect(output).toBeDefined();
expect(output.UserPools).toEqual([
{
Id: userPool1.Id,
},
{
Id: userPool2.Id,
},
]);
});
});
27 changes: 27 additions & 0 deletions src/targets/listUserPools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
ListUserPoolsRequest,
ListUserPoolsResponse,
} from "aws-sdk/clients/cognitoidentityserviceprovider";
import { Services } from "../services";

export type ListUserPoolsTarget = (
req: ListUserPoolsRequest
) => Promise<ListUserPoolsResponse>;

type ListGroupServices = Pick<Services, "cognito">;

export const ListUserPools = ({
cognito,
}: ListGroupServices): ListUserPoolsTarget => async () => {
// TODO: NextToken support
// TODO: MaxResults support

const userPools = await cognito.listUserPools();

return {
UserPools: userPools.map((x) => ({
Id: x.Id,
// TODO: support more attributes on UserPool
})),
};
};
2 changes: 2 additions & 0 deletions src/targets/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ForgotPassword } from "./forgotPassword";
import { ChangePassword } from "./changePassword";
import { InitiateAuth } from "./initiateAuth";
import { ListGroups } from "./listGroups";
import { ListUserPools } from "./listUserPools";
import { ListUsers } from "./listUsers";
import { RespondToAuthChallenge } from "./respondToAuthChallenge";
import { SignUp } from "./signUp";
Expand Down Expand Up @@ -41,6 +42,7 @@ export const Targets = {
InitiateAuth,
ListGroups,
ListUsers,
ListUserPools,
RespondToAuthChallenge,
SignUp,
};
Expand Down

0 comments on commit d2e5324

Please sign in to comment.