-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Write events test suite #181
Conversation
Thanks for contributing! If you've made changes to the API's functionality, please make sure to bump the package version—see this guide to semantic versioning for details—and document those changes as appropriate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall. I think we should mock StorageService
instead of actually writing/reading to an S3 bucket (and definitely not to the same folder that prod uses), and we should start passing in our configs to constructors to make it easier to mock things.
@@ -18,7 +18,7 @@ export default class EventService { | |||
public async create(event: Event) { | |||
const eventCreated = await this.transactions.readWrite(async (txn) => { | |||
const eventRepository = Repositories.event(txn); | |||
const isUnusedAttendanceCode = eventRepository.isUnusedAttendanceCode(event.attendanceCode); | |||
const isUnusedAttendanceCode = await eventRepository.isUnusedAttendanceCode(event.attendanceCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you catch this with a test or just notice it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was caught with test - I saw that the duplicate attendance code error was thrown by Postgres' duplicate key constraint and not by the event service
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, our first bug caught by testing!
tests/data/EventFactory.ts
Outdated
return EventFactory.fakeWithTime(start, end); | ||
} | ||
|
||
private static fakeWithTime(start: Date, end: Date): EventModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the with
method instead of fakeWithTime
, e.g.
private static fakeWithTime(start: Date, end: Date): EventModel {
return with({ start, end });
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally forgot about with()
- we actually don't even need fakeWithTime
or fakeWithoutTime
anymore if with()
is used for fakePastEvent(), fakeOngoingEvent(),
and fakeFutureEvent()
.
tests/data/EventFactory.ts
Outdated
return event; | ||
} | ||
|
||
public static fakeWithoutTime(): EventModel { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the different between this and fake
? Or does this method exist because of fakeWithTime
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned above, this method is not needed anymore
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the idea before was to have fake, fakePastEvent, fakeOngoingEvent, fakeFutureEvent
to all call fakeWithTime
, but specifying different times for each section. fakeWithTime
would then call fakeWithoutTime
to get a random object for all other fields
return [start.toDate(), end.toDate()]; | ||
} | ||
|
||
private static randomPastTime(daysAgo: number): [Date, Date] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we reuse code between these randomTime
methods? Maybe we have optional params where we can set some days
, hour
, and duration
fields?
tests/data/FeedbackFactory.ts
Outdated
@@ -19,6 +19,10 @@ export class FeedbackFactory { | |||
}; | |||
} | |||
|
|||
public static createEventFeedback(n: number): string[] { | |||
return new Array(n).fill(faker.random.word()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a random word or some value from a predefined array of values? IIRC feedback's supposed to be some predefined emojis.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure the predefined emojis have been confirmed yet since a lot of the design materials are moving around right now, and membership hasn't acknowledged them either since the portal redesign has been pushed back significantly (till after the store frontend is finished). I think it's fine to keep them at random words for now and then adjust later once the set of emojis /text is confirmed.
tests/event.test.ts
Outdated
.createEvents([event]) | ||
.write(); | ||
|
||
const eventCoverResponse = await ControllerFactory.event(conn).updateEventCover(cover, event.uuid, admin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think our tests should write to our prod bucket. At least, definitely not to the same folder. This is what I said a while back, that we should pass in our configs through the constructor, so we can specify where things get written to instead of being tied to whatever's in the prod config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure what we should pass in to the StorageService
constructor, since I believe most of our config (such as bucket to be written to) is unchangeable. The S3_BUCKET, S3_REGION
, and S3_CREDENTIALS
are all constant through the entire app, and there's separate buckets for local (acmucsd-local
, for what is used for locally running the test suite and in CircleCI jobs), testing (acmucsd-membership-portal
, for testing.api.acmucsd.com), and prod (acmucsd
, for what is used in production), so tests don't actually get written to our prod bucket.
const ONE_BYTE_FILLER = 'a'; | ||
|
||
// Factory for mocking in-memory files for testing with multer | ||
export class FileFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we do test that we're writing/reading from S3 should we also verify the file is exactly the same? If so, maybe we write a random string and verify checksums? I don't think we need to actually interact with S3 though, mocking should be good enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, mocking should be good enough.
tests/event.test.ts
Outdated
await ControllerFactory.event(conn).submitEventFeedback(event.uuid, { feedback }, user); | ||
|
||
const attendanceResponse = await ControllerFactory.attendance(conn).getAttendancesForCurrentUser(user); | ||
const attendance = attendanceResponse.attendances[0]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: inline.
tests/event.test.ts
Outdated
.write(); | ||
|
||
const eventController = ControllerFactory.event(conn); | ||
const errorMessage = 'Attendance code has already been used'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: inline this and the other error messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good overall though I'm not as intimately familiar with our test suite (whether there are better functions to use) and the higher-level design
Co-authored-by: Sumeet Bansal <[email protected]>
@sumeet-bansal how do you think we should go about mocking |
What do you mean by wrapping controllers? Jest should have some sort of mocking functionality where we can do something like const storageService = mock(IStorageService.class);
when(storageService.upload(any(), any(), eq(MediaType.EVENT_COVER), eq("some-uuid")).thenReturn("fileLocation"); (That's the syntax for the Mockito library in Java.) But the idea is, we create a "mock" and then we can define custom responses based on the parameters, and instead of executing normally, the mock returns our defined response. |
This looks like what we need (link). |
Mock |
Wait, why mock |
I might just be generally confused of the concept/use case of mocking cause I think I've been the word throwing around here and there with the controllers without knowing if that's what we're actually doing with them. |
No you're right, we could mock |
Yeah it's like that since that's how jest does mocking for modules. If we were to mock StorageService instead, then all we can assert is just that |
|
…e optional before but implicitly)
if (changes.attendanceCode !== currentEvent.attendanceCode) { | ||
const isUnusedAttendanceCode = eventRepository.isUnusedAttendanceCode(changes.attendanceCode); | ||
const isUnusedAttendanceCode = await eventRepository.isUnusedAttendanceCode(changes.attendanceCode); | ||
if (!isUnusedAttendanceCode) throw new UserError('Attendance code has already been used'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package.json
Outdated
@@ -56,6 +56,7 @@ | |||
"pg": "^8.6.0", | |||
"reflect-metadata": "^0.1.13", | |||
"routing-controllers": "^0.9.0", | |||
"ts-mockito": "^2.6.1", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: dev dependency.
tests/controllers/index.ts
Outdated
import { Connection } from 'typeorm'; | ||
import FeedbackService from '../../services/FeedbackService'; | ||
import { FeedbackController } from '../../api/controllers/FeedbackController'; | ||
import { UserController } from '../../api/controllers/UserController'; | ||
import UserAccountService from '../../services/UserAccountService'; | ||
import StorageService from '../../services/StorageService'; | ||
import { AdminController } from '../../api/controllers/AdminController'; | ||
import AttendanceService from '../../services/AttendanceService'; | ||
import { AttendanceController } from '../../api/controllers/AttendanceController'; | ||
import { AuthController } from '../../api/controllers/AuthController'; | ||
import { EventController } from '../../api/controllers/EventController'; | ||
import { LeaderboardController } from '../../api/controllers/LeaderboardController'; | ||
import { MerchStoreController } from '../../api/controllers/MerchStoreController'; | ||
import UserAuthService from '../../services/UserAuthService'; | ||
import EmailService from '../../services/EmailService'; | ||
import EventService from '../../services/EventService'; | ||
import MerchStoreService from '../../services/MerchStoreService'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: can we clean these imports up a bit in a follow-up PR? Let's import by api/controllers
and services
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, just created an issue for it
tests/controllers/index.ts
Outdated
import EventService from '../../services/EventService'; | ||
import MerchStoreService from '../../services/MerchStoreService'; | ||
|
||
export class ControllerFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is getting messy. Let's remove the controller singletons and initialize a new controller each time the factory's called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. Is the reason we are willing to trade-off readability for memory because this class is used only in tests and isn't persisting any memory afterwards? (meaning we are okay using a small amount more of memory since it's all present only during the tests running)
tests/controllers/index.ts
Outdated
import EventService from '../../services/EventService'; | ||
import MerchStoreService from '../../services/MerchStoreService'; | ||
|
||
export class ControllerFactory { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: rename file to the class name.
@@ -24,11 +23,6 @@ export class Feedback implements IFeedback { | |||
type: FeedbackType; | |||
} | |||
|
|||
export class SubmitEventFeedbackRequest implements ISubmitEventFeedbackRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's this change for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved SubmitEventFeedbackRequest from FeedbackControllerRequests to EventControllerRequests
const attendanceResponse = await ControllerFactory.attendance(conn).getAttendancesForCurrentUser(user); | ||
|
||
expect(attendanceResponse.attendances[0].feedback).toEqual(feedback); | ||
expect(user.points).toEqual(event.pointValue + Config.pointReward.EVENT_FEEDBACK_POINT_REWARD); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is this passing? Shouldn't we need to refresh the UserModel
before the points field is updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/acmucsd/membership-portal/blob/master/repositories/UserRepository.ts#L52
This line updates the points field directly on the user
object, so no refresh is necessary.
expect(errors).toBeDefined(); | ||
expect(errors).toHaveLength(1); | ||
expect(errors[0].property).toEqual('feedback'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add an expect
that checks that the reason validation failed is because >3?
const uuid = submittedFeedbackResponse.feedback; | ||
const acknowledgedFeedback = await feedbackController.updateFeedbackStatus(uuid, { status }, admin); | ||
const { uuid } = submittedFeedbackResponse.feedback; | ||
const acknowledgedFeedback = await feedbackController.updateFeedbackStatus({ uuid }, { status }, admin); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see any changes to the feedback controller so why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it was a missing change from this PR that wasn't causing the test to fail for some reason.
import StorageService from '../../services/StorageService'; | ||
|
||
export default class Mocks { | ||
public static storage(fileLocation: string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Inline this and use verify
to check that we called the mock correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not too sure what you mean by this. What should we be inlining, and where should we be verifying the call?
tests/event.test.ts
Outdated
.event(conn, null, Mocks.storage(fileLocation)) | ||
.updateEventCover(cover, { uuid: event.uuid }, admin); | ||
|
||
expect(eventCoverResponse.event.cover).toEqual(fileLocation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should get the event and check the cover from that instead of checking the updateEventCoverResponse
.
* 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 #181 Code from #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]>
Closing this for now since this will not be worked on for quite a bit, as merch store issues are being worked on for the October launch. I'll reopen this once things have settled with the store. This branch should not be deleted |
* 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]>
Closes #175