Skip to content

Commit

Permalink
feat(api): createGroup support
Browse files Browse the repository at this point in the history
  • Loading branch information
jagregory committed Nov 24, 2021
1 parent 64390e5 commit c3dc092
Show file tree
Hide file tree
Showing 21 changed files with 410 additions and 215 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A _Good Enough_ offline emulator for [Amazon Cognito](https://aws.amazon.com/cog
- [ChangePassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ChangePassword.html) (community contributed, incomplete)
- [ConfirmForgotPassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmForgotPassword.html)
- [ConfirmSignUp](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ConfirmSignUp.html)
- [CreateGroup](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateGroup.html)
- [CreateUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_CreateUserPoolClient.html)
- [DescribeUserPoolClient](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_DescribeUserPoolClient.html)
- [ForgotPassword](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_ForgotPassword.html)
Expand Down
65 changes: 65 additions & 0 deletions integration-tests/aws-sdk/createGroup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { ClockFake } from "../../src/__tests__/clockFake";
import { UUID } from "../../src/__tests__/patterns";
import { withCognitoSdk } from "./setup";

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

const clock = new ClockFake(currentDate);

describe(
"CognitoIdentityServiceProvider.createGroup",
withCognitoSdk(
(Cognito) => {
it("creates a group with only the required parameters", async () => {
const client = Cognito();

const createGroupResult = await client
.createGroup({
GroupName: "abc",
UserPoolId: "test",
})
.promise();

expect(createGroupResult).toEqual({
Group: {
CreationDate: roundedDate,
GroupName: "abc",
LastModifiedDate: roundedDate,
UserPoolId: "test",
},
});
});

it("creates a group with all parameters", async () => {
const client = Cognito();

const createGroupResult = await client
.createGroup({
Description: "Description",
GroupName: "abc",
Precedence: 1,
RoleArn: "arn",
UserPoolId: "test",
})
.promise();

expect(createGroupResult).toEqual({
Group: {
CreationDate: roundedDate,
Description: "Description",
GroupName: "abc",
LastModifiedDate: roundedDate,
Precedence: 1,
RoleArn: "arn",
UserPoolId: "test",
},
});
});
},
{
clock,
}
)
);
17 changes: 17 additions & 0 deletions src/__tests__/mockUserPoolClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { UserPoolClient } from "../services";

export const MockUserPoolClient = {
config: {
Id: "test",
} as Record<string, string>,
createAppClient: jest.fn() as jest.MockedFunction<
UserPoolClient["createAppClient"]
>,
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"]>,
saveGroup: jest.fn() as jest.MockedFunction<UserPoolClient["saveGroup"]>,
};
15 changes: 3 additions & 12 deletions src/services/triggers/customMessage.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MockLogger } from "../../__tests__/mockLogger";
import { MockUserPoolClient } from "../../__tests__/mockUserPoolClient";
import { CognitoClient } from "../cognitoClient";
import { Lambda } from "../lambda";
import { UserPoolClient } from "../userPoolClient";
Expand All @@ -7,27 +8,17 @@ import { CustomMessage, CustomMessageTrigger } from "./customMessage";
describe("CustomMessage trigger", () => {
let mockLambda: jest.Mocked<Lambda>;
let mockCognitoClient: jest.Mocked<CognitoClient>;
let mockUserPoolClient: jest.Mocked<UserPoolClient>;
let customMessage: CustomMessageTrigger;

beforeEach(() => {
mockLambda = {
enabled: jest.fn(),
invoke: jest.fn(),
};
mockUserPoolClient = {
config: {
Id: "test",
},
createAppClient: jest.fn(),
getUserByUsername: jest.fn(),
listUsers: jest.fn(),
saveUser: jest.fn(),
};
mockCognitoClient = {
getAppClient: jest.fn(),
getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPool: jest.fn().mockResolvedValue(MockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(MockUserPoolClient),
};

customMessage = CustomMessage(
Expand Down
15 changes: 3 additions & 12 deletions src/services/triggers/postConfirmation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MockLogger } from "../../__tests__/mockLogger";
import { MockUserPoolClient } from "../../__tests__/mockUserPoolClient";
import { Lambda } from "../lambda";
import { UserPoolClient } from "../userPoolClient";
import { PostConfirmation, PostConfirmationTrigger } from "./postConfirmation";
Expand All @@ -7,27 +8,17 @@ import { CognitoClient } from "../cognitoClient";
describe("PostConfirmation trigger", () => {
let mockLambda: jest.Mocked<Lambda>;
let mockCognitoClient: jest.Mocked<CognitoClient>;
let mockUserPoolClient: jest.Mocked<UserPoolClient>;
let postConfirmation: PostConfirmationTrigger;

beforeEach(() => {
mockLambda = {
enabled: jest.fn(),
invoke: jest.fn(),
};
mockUserPoolClient = {
config: {
Id: "test",
},
createAppClient: jest.fn(),
getUserByUsername: jest.fn(),
listUsers: jest.fn(),
saveUser: jest.fn(),
};
mockCognitoClient = {
getAppClient: jest.fn(),
getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPool: jest.fn().mockResolvedValue(MockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(MockUserPoolClient),
};
postConfirmation = PostConfirmation(
{
Expand Down
15 changes: 3 additions & 12 deletions src/services/triggers/userMigration.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MockUserPoolClient } from "../../__tests__/mockUserPoolClient";
import { UUID } from "../../__tests__/patterns";
import { NotAuthorizedError } from "../../errors";
import { DateClock } from "../clock";
Expand All @@ -9,27 +10,17 @@ import { UserMigration, UserMigrationTrigger } from "./userMigration";
describe("UserMigration trigger", () => {
let mockLambda: jest.Mocked<Lambda>;
let mockCognitoClient: jest.Mocked<CognitoClient>;
let mockUserPoolClient: jest.Mocked<UserPoolClient>;
let userMigration: UserMigrationTrigger;

beforeEach(() => {
mockLambda = {
enabled: jest.fn(),
invoke: jest.fn(),
};
mockUserPoolClient = {
config: {
Id: "test",
},
createAppClient: jest.fn(),
getUserByUsername: jest.fn(),
listUsers: jest.fn(),
saveUser: jest.fn(),
};
mockCognitoClient = {
getAppClient: jest.fn(),
getUserPool: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(mockUserPoolClient),
getUserPool: jest.fn().mockResolvedValue(MockUserPoolClient),
getUserPoolForClientId: jest.fn().mockResolvedValue(MockUserPoolClient),
};

userMigration = UserMigration({
Expand Down
89 changes: 89 additions & 0 deletions src/services/userPoolClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
User,
UserPoolClient,
UserPoolClientService,
Group,
} from "./userPoolClient";

describe("User Pool Client", () => {
Expand Down Expand Up @@ -358,4 +359,92 @@ describe("User Pool Client", () => {
});
});
});

describe("saveGroup", () => {
it("saves the group", async () => {
const now = new Date().getTime();
const set = jest.fn();

const userPool = await UserPoolClientService.create(
mockClientsDataStore,
clock,
() =>
Promise.resolve({
set,
get: jest.fn(),
getRoot: jest.fn(),
}),
{ Id: "local", UsernameAttributes: [] },
MockLogger
);

await userPool.saveGroup({
CreationDate: now,
Description: "Description",
GroupName: "theGroupName",
LastModifiedDate: now,
Precedence: 1,
RoleArn: "ARN",
});

expect(set).toHaveBeenCalledWith(["Groups", "theGroupName"], {
CreationDate: now,
Description: "Description",
GroupName: "theGroupName",
LastModifiedDate: now,
Precedence: 1,
RoleArn: "ARN",
});
});
});

describe("listGroups", () => {
let userPool: UserPoolClient;

beforeEach(async () => {
const options = {
Id: "local",
};
const groups: Record<string, Group> = {
theGroupName: {
CreationDate: new Date().getTime(),
Description: "Description",
GroupName: "theGroupName",
LastModifiedDate: new Date().getTime(),
Precedence: 1,
RoleArn: "ARN",
},
};

const get = jest.fn((key) => {
if (key === "Groups") {
return Promise.resolve(groups);
} else if (key === "Options") {
return Promise.resolve(options);
}

return Promise.resolve(null);
});
userPool = await UserPoolClientService.create(
mockClientsDataStore,
clock,
() =>
Promise.resolve({
set: jest.fn(),
get,
getRoot: jest.fn(),
}),
options,
MockLogger
);
});

it("returns existing groups", async () => {
const groups = await userPool.listGroups();

expect(groups).not.toBeNull();
expect(groups).toHaveLength(1);
expect(groups[0].GroupName).toEqual("theGroupName");
});
});
});
45 changes: 45 additions & 0 deletions src/services/userPoolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ export interface User {
MFACode?: string;
}

export interface Group {
/**
* The name of the group.
*/
GroupName: string;
/**
* A string containing the description of the group.
*/
Description?: string;
/**
* The role ARN for the group.
*/
RoleArn?: string;
/**
* A nonnegative integer value that specifies the precedence of this group relative to the other groups that a user can belong to in the user pool. If a user belongs to two or more groups, it is the group with the highest precedence whose role ARN will be used in the cognito:roles and cognito:preferred_role claims in the user's tokens. Groups with higher Precedence values take precedence over groups with lower Precedence values or with null Precedence values. Two groups can have the same Precedence value. If this happens, neither group takes precedence over the other. If two groups with the same Precedence have the same role ARN, that role is used in the cognito:preferred_role claim in tokens for users in each group. If the two groups have different role ARNs, the cognito:preferred_role claim is not set in users' tokens. The default Precedence value is null.
*/
Precedence?: number;
/**
* The date the group was last modified.
*/
LastModifiedDate: number;
/**
* The date the group was created.
*/
CreationDate: number;
}

type UsernameAttribute = "email" | "phone_number";

export interface UserPool {
Expand All @@ -70,6 +97,8 @@ export interface UserPoolClient {
getUserByUsername(username: string): Promise<User | null>;
listUsers(): Promise<readonly User[]>;
saveUser(user: User): Promise<void>;
listGroups(): Promise<readonly Group[]>;
saveGroup(group: Group): Promise<void>;
}

export type CreateUserPoolClient = (
Expand Down Expand Up @@ -193,4 +222,20 @@ export class UserPoolClientService implements UserPoolClient {

await this.dataStore.set<User>(["Users", user.Username], user);
}

async listGroups(): Promise<readonly Group[]> {
this.logger.debug("listGroups");
const groups = await this.dataStore.get<Record<string, Group>>(
"Groups",
{}
);

return Object.values(groups);
}

async saveGroup(group: Group) {
this.logger.debug("saveGroup", group);

await this.dataStore.set<Group>(["Groups", group.GroupName], group);
}
}
Loading

0 comments on commit c3dc092

Please sign in to comment.