Skip to content

Commit

Permalink
Add AdminController Test Suite (acmucsd#187)
Browse files Browse the repository at this point in the history
* Add tests for "/bonus" and "/emails" routes

Squashed set of commits for 6f51db8...c70e444. Merge conflict
was on package-lock, which was hard to fix, so I just redid the commits.

* nit: "proxyUser" -> "adminUser" for admin tests

* Adjust array equality checks on admin tests

* Remove test for non-present emails

Test is not necessary due to lack of filtering in original controller.

* Inline functions and variables in admin tests

* Modify test to check bonus points for all users

* Combine both bonus points tests into one

Tests had basically the same boilerplate. Combine them to check both
conditions at once.

The test already checked if we were adding bonus point to _specifically_
the emails we passed in, so it's best to also check if an extra user
wasn't included at the same time.

* Rearrange asserts to suffix response calls

* Rename adminUser to admin in admin test suite

* Adjust order of assertions in admin test

* Adjust name of extraneous user in admin test

* Make variable for UserController in admin test

Inlining UserController made one line too long.
Since it is used multiple times in the test, extract
variable to make its usage less verbose.

* Fix nits and check attendance response details

* Check for correct activities in admin test suite

* Add points field of ActivityModel in test suite

ActivityModel is missing the `pointsEarned` field whenever creating
an Activity after attending an event. This only happens in our mock
tester.

Add a check for this field's presence, along with fixing the inherent
bug within the mock state.

* Port EventFactory code from acmucsd#181

Code from acmucsd#181 adds ability to fake events in the past, future or present.
Taken verbatim from commit 18a952c.

* Adjust test suite to account for factory bug

Account creation can occur after event attendance, causing activities
to be out of order chronologically. Guarantee order in activities by
introducing retroactive attendance in a future event.

Bug in factory is not a reproducible issue in production, since account
creation will always occur before event attendance in real time.

* Revert event factory code changes

* Adjust transaction order when saving factory state

Mock factory `write` function incorrectly saves attendance before
any possible activities, typically causing event attendances to be
stored in the database prior to activities being stored. Adjust order
of flushing to database in order to consistently set smaller timestamps
for account-related activities, such as account creation.

* Revert transaction order adjustment

This reverts commit b5f03dc.

Tests continue to be non-deterministic despite this change. My
assumption was wrong.

* Guarantee correct activity order in failed tests

Create users at beginning of the Unix epoch to ensure guarantee
of event attendance activity being second is fulfilled.

Since Activities are sorted by timestamp, we need to guarantee account
creation does not occur before attendance of event. Fix issue with above
workaround, providing explicit method whenever necessary.

* Merge user creation methods to have optional args

Rather than having two explicit methods, merge methods and use optional
timestamp argument to allow for custom creation of users at explicit times.

* Adjust user creation method to have set timestamp

Use specified timestamp of 1 month before current time to place account
creation in time before most activities for testing.

* Fix argument call for creating users in test suite

Co-authored-by: Matei-Alexandru Gardus <[email protected]>
  • Loading branch information
StormFireFox1 and Matei-Alexandru Gardus authored Aug 2, 2021
1 parent 62cc782 commit 86f095a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 21 deletions.
107 changes: 86 additions & 21 deletions tests/admin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ActivityScope, ActivityType, SubmitAttendanceForUsersRequest, UserAccessType } from '../types';
import { ControllerFactory } from './controllers';
import { DatabaseConnection, EventFactory, UserFactory, PortalState } from './data';
import { DatabaseConnection, EventFactory, PortalState, UserFactory } from './data';

beforeAll(async () => {
await DatabaseConnection.connect();
Expand All @@ -20,31 +20,31 @@ describe('retroactive attendance submission', () => {
const conn = await DatabaseConnection.get();
const users = UserFactory.create(3);
const emails = users.map((user) => user.email);
const [proxyUser] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [admin] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [event] = EventFactory.create(1);

await new PortalState()
.createUsers([...users, proxyUser])
.createUsers([...users, admin])
.createEvents([event])
.write();

const userController = ControllerFactory.user(conn);
const adminController = ControllerFactory.admin(conn);
const attendanceController = ControllerFactory.attendance(conn);

await adminController.submitAttendanceForUsers({ users: emails, event: event.uuid }, proxyUser);
await adminController.submitAttendanceForUsers({ users: emails, event: event.uuid }, admin);

for (let u = 0; u < users.length; u += 1) {
const user = users[u];
const userResponse = await userController.getUser({ uuid: user.uuid }, proxyUser);
const userResponse = await userController.getUser({ uuid: user.uuid }, admin);

expect(userResponse.user.points).toEqual(user.points + event.pointValue);

const attendanceResponse = await attendanceController.getAttendancesForCurrentUser(user);
expect(attendanceResponse.attendances).toHaveLength(1);
expect(attendanceResponse.attendances[0].event).toStrictEqual(event.getPublicEvent());

const activityResponse = await userController.getUserActivityStream({ uuid: user.uuid }, proxyUser);
const activityResponse = await userController.getUserActivityStream({ uuid: user.uuid }, admin);

expect(activityResponse.activity).toHaveLength(2);
expect(activityResponse.activity[1].pointsEarned).toEqual(event.pointValue);
Expand All @@ -56,11 +56,14 @@ describe('retroactive attendance submission', () => {
test('does not log activity, attendance, and points for users who already attended', async () => {
const conn = await DatabaseConnection.get();
const [user] = UserFactory.create(1);
const [proxyUser] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [admin] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [event] = EventFactory.create(1);

await new PortalState()
.createUsers([user, proxyUser])
// Create users at beginning of time to ensure that account creation time does not
// end up after event attendance time, causing the order guarantee for activities
// below to fail.
.createUsers([user, admin])
.createEvents([event])
.attendEvents([user], [event])
.write();
Expand All @@ -71,28 +74,33 @@ describe('retroactive attendance submission', () => {

await adminController.submitAttendanceForUsers(
{ users: [user.email], event: event.uuid },
proxyUser,
admin,
);

const userResponse = await userController.getUser({ uuid: user.uuid }, proxyUser);
const attendanceResponse = await attendanceController.getAttendancesForCurrentUser(user);
const activityResponse = await userController.getCurrentUserActivityStream(user);

const userResponse = await userController.getUser({ uuid: user.uuid }, admin);
expect(userResponse.user.points).toEqual(user.points);

const attendanceResponse = await attendanceController.getAttendancesForCurrentUser(user);
expect(attendanceResponse.attendances).toHaveLength(1);
expect(attendanceResponse.attendances[0].event.uuid).toEqual(event.uuid);
expect(attendanceResponse.attendances[0].asStaff).toEqual(false);

const activityResponse = await userController.getCurrentUserActivityStream(user);
expect(activityResponse.activity).toHaveLength(2);
expect(activityResponse.activity[1].description).toBeNull();
expect(activityResponse.activity[1].type).toEqual(ActivityType.ATTEND_EVENT);
expect(activityResponse.activity[1].pointsEarned).toEqual(event.pointValue);
});

test('logs proper activity and point rewards for staff attendance', async () => {
const conn = await DatabaseConnection.get();
const [user] = UserFactory.create(1);
const [staffUser] = UserFactory.with({ accessType: UserAccessType.STAFF });
const [proxyUser] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [admin] = UserFactory.with({ accessType: UserAccessType.ADMIN });
const [event] = EventFactory.with({ requiresStaff: true, staffPointBonus: 10 });

await new PortalState()
.createUsers([user, staffUser, proxyUser])
.createUsers([user, staffUser, admin])
.createEvents([event])
.write();

Expand All @@ -104,16 +112,73 @@ describe('retroactive attendance submission', () => {
asStaff: true,
};

await adminController.submitAttendanceForUsers(request, proxyUser);

const userResponse = await userController.getUser({ uuid: user.uuid }, proxyUser);
const staffUserResponse = await userController.getUser({ uuid: staffUser.uuid }, proxyUser);
const activityResponse = await userController.getCurrentUserActivityStream(user);
const staffActivityResponse = await userController.getCurrentUserActivityStream(staffUser);
await adminController.submitAttendanceForUsers(request, admin);

const userResponse = await userController.getUser({ uuid: user.uuid }, admin);
expect(userResponse.user.points).toEqual(event.pointValue);

const staffUserResponse = await userController.getUser({ uuid: staffUser.uuid }, admin);
expect(staffUserResponse.user.points).toEqual(event.pointValue + event.staffPointBonus);

const activityResponse = await userController.getCurrentUserActivityStream(user);
expect(activityResponse.activity[1].type).toEqual(ActivityType.ATTEND_EVENT);

const staffActivityResponse = await userController.getCurrentUserActivityStream(staffUser);
expect(staffActivityResponse.activity[1].type).toEqual(ActivityType.ATTEND_EVENT_AS_STAFF);
});
});

describe('email retrieval', () => {
test('gets all the emails of stored users', async () => {
const conn = await DatabaseConnection.get();
const users = UserFactory.create(5);
const emails = users.map((user) => user.email.toLowerCase());
const [admin] = UserFactory.with({ accessType: UserAccessType.ADMIN });

await new PortalState()
.createUsers([...users, admin])
.write();

const response = await ControllerFactory.admin(conn).getAllEmails(admin);
expect(expect.arrayContaining(response.emails)).toEqual([...emails, admin.email]);
});
});

describe('bonus points submission', () => {
test('updates points and activity to the users in the bonus request', async () => {
const conn = await DatabaseConnection.get();
const [userNotGettingBonus, ...users] = UserFactory.create(5);
const emails = users.map((user) => user.email.toLowerCase());
const [admin] = UserFactory.with({ accessType: UserAccessType.ADMIN });

await new PortalState()
.createUsers([...users, userNotGettingBonus, admin])
.write();

const bonus = {
description: 'Test addition of bonus points',
users: emails,
points: 200,
};

const createBonusResponse = await ControllerFactory.admin(conn).addBonus({ bonus }, admin);
expect(createBonusResponse.emails).toEqual(expect.arrayContaining(emails));

const userController = ControllerFactory.user(conn);

for (let u = 0; u < users.length; u += 1) {
const user = users[u];
const getUserResponse = await userController.getUser({ uuid: user.uuid }, admin);
expect(getUserResponse.user.points).toEqual(200);

const activityResponse = await userController.getCurrentUserActivityStream(user);
expect(activityResponse.activity).toHaveLength(2);
expect(activityResponse.activity[1].description).toEqual(bonus.description);
expect(activityResponse.activity[1].type).toEqual(ActivityType.BONUS_POINTS);
expect(activityResponse.activity[1].pointsEarned).toEqual(bonus.points);
}

const getNoBonusUserResponse = await userController.getUser({ uuid: userNotGettingBonus.uuid }, admin);
expect(getNoBonusUserResponse.user.points).toEqual(0);
});
});
3 changes: 3 additions & 0 deletions tests/data/PortalState.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as rfdc from 'rfdc';
import { flatten } from 'underscore';
import * as moment from 'moment';
import { AttendanceModel } from '../../models/AttendanceModel';
import { EventModel } from '../../models/EventModel';
import { MerchandiseCollectionModel } from '../../models/MerchandiseCollectionModel';
Expand Down Expand Up @@ -61,6 +62,7 @@ export class PortalState {
user,
type: ActivityType.ACCOUNT_CREATE,
scope: ActivityScope.PUBLIC,
timestamp: moment().subtract(1, 'months'),
}));
}
return this;
Expand Down Expand Up @@ -97,6 +99,7 @@ export class PortalState {
type: asStaff ? ActivityType.ATTEND_EVENT_AS_STAFF : ActivityType.ATTEND_EVENT,
timestamp,
scope: ActivityScope.PUBLIC,
pointsEarned,
}));
}
}
Expand Down

0 comments on commit 86f095a

Please sign in to comment.