Conversation
What was broken\nFinance payment generation treated leaderboard-only marathon fun challenges like normal payable challenges and attempted to create winnings records.\n\nRoot cause\nNo payment-generation guard existed for funChallenge in challenge processing, and the challenge model did not expose the flag.\n\nWhat was changed\nAdded funChallenge to the challenge model and short-circuited generateChallengePayments when it is true. Added Jest module alias mapping and minimal compile-safe typing fixes needed to execute the new unit test in this repo.\n\nAny added/updated tests\nAdded ChallengesService unit test to verify payment generation is skipped and no challenge lock is created for fun challenges.
What was broken:\nDate filters for wallet admin payments (last 7/30 days) did not narrow results when records had future created_at dates.\n\nRoot cause:\nThe date filter in WinningsRepository only applied a lower bound (gte) and had no upper bound, so future-dated rows still matched last7days/last30days.\n\nWhat was changed:\nUpdated generateFilterDate to apply both gte and lte bounds for LAST7DAYS and LAST30DAYS, with lte set to end-of-today.\nAdded repository unit tests to verify date windows for LAST7DAYS, LAST30DAYS, and ALL.\n\nAny added/updated tests:\nAdded src/api/repository/winnings.repo.spec.ts covering date filter range behavior in searchWinnings.
What was broken: Challenge payment lookups in the review flow were forced to winner-only results for non-privileged users, so challenge managers reviewing completed challenges could see "No payments found" even when payments existed. Root cause: Challenge-wide access expansion only checked copilot roles. Manager/full-write challenge resource roles were not treated as challenge-wide readers. What was changed: Updated challenge-payments access checks to allow full challenge visibility for users with challenge-wide roles (fullWriteAccess roles plus manager/copilot role names). Kept winner-based filtering for users without challenge-wide access. Hardened role claim parsing to only stringify primitive claim values. Extended the challenge resource-role model with optional fullWriteAccess for this access check. Any added/updated tests: Added unit tests for ChallengePaymentsService to verify manager access returns all challenge payments and non-challenge-wide users remain winner-filtered.
| @@ -0,0 +1 @@ | |||
| CREATE INDEX idx_winnings_type_created_at ON winnings USING btree (type, created_at DESC); | |||
There was a problem hiding this comment.
[correctness]
Consider adding a conditional check to ensure that the winnings table exists before attempting to create the index. This can prevent potential errors during the migration if the table is not present.
| winnings: { | ||
| findMany: findManyMock, | ||
| }, | ||
| } as any, |
There was a problem hiding this comment.
[maintainability]
Casting to any can obscure type errors and reduce type safety. Consider using a more specific type or interface for the winnings and m2mFetch objects to enhance maintainability and type safety.
| } as any, | ||
| { | ||
| m2mFetch: m2mFetchMock, | ||
| } as any, |
There was a problem hiding this comment.
[maintainability]
Casting to any can obscure type errors and reduce type safety. Consider using a more specific type or interface for the winnings and m2mFetch objects to enhance maintainability and type safety.
| winnerOnly: false, | ||
| }); | ||
|
|
||
| const where = findManyMock.mock.calls[0][0].where; |
There was a problem hiding this comment.
[correctness]
Accessing mock.calls[0][0].where directly assumes that the mock has been called at least once and that the structure of the call arguments is as expected. Consider adding checks or using a more robust way to verify the mock call structure to prevent potential runtime errors.
| winnerOnly: false, | ||
| }); | ||
|
|
||
| const where = findManyMock.mock.calls[0][0].where; |
There was a problem hiding this comment.
[correctness]
Accessing mock.calls[0][0].where directly assumes that the mock has been called at least once and that the structure of the call arguments is as expected. Consider adding checks or using a more robust way to verify the mock call structure to prevent potential runtime errors.
| requestUserId, | ||
| ); | ||
| if (isCopilot) { | ||
| const hasChallengeWideAccess = |
There was a problem hiding this comment.
[💡 readability]
The method name hasChallengeWideAccessForChallenge is somewhat redundant and could be simplified to hasChallengeWideAccess. This would improve readability by reducing verbosity without losing clarity.
| } | ||
| }); | ||
| } else if (value) { | ||
| } else if ( |
There was a problem hiding this comment.
[correctness]
The condition checks for typeof value being string, number, or boolean. Consider whether other types (e.g., object, function) should be explicitly handled or if they should trigger a warning or error. This could prevent unexpected behavior if value is of an unhandled type.
| } | ||
|
|
||
| private async isCopilotForChallenge( | ||
| private async hasChallengeWideAccessForChallenge( |
There was a problem hiding this comment.
[💡 readability]
The method name hasChallengeWideAccessForChallenge is somewhat redundant and could be misleading. Consider renaming it to hasChallengeWideAccess for clarity and maintainability.
| role?.name ? role.name.toLowerCase().includes('copilot') : false, | ||
| ?.filter( | ||
| (role) => | ||
| role?.fullWriteAccess === true || |
There was a problem hiding this comment.
[correctness]
The condition role?.fullWriteAccess === true is checked before checking the role name. If fullWriteAccess is a boolean, this is fine, but if it can be null or undefined, consider using a more robust check like role?.fullWriteAccess to ensure it handles all falsy values correctly.
| (role) => | ||
| role?.fullWriteAccess === true || | ||
| (role?.name | ||
| ? role.name.toLowerCase().includes('copilot') || |
There was a problem hiding this comment.
[💡 readability]
The use of toLowerCase() on role.name is repeated multiple times. Consider extracting this to a variable to improve readability and avoid redundant operations.
| }, | ||
| }; | ||
| const service = new ChallengesService( | ||
| prisma as any, |
There was a problem hiding this comment.
[maintainability]
Using as any for type casting can lead to potential type safety issues. Consider defining proper interfaces or types for the dependencies to ensure type safety.
| status: ChallengeStatuses.Completed, | ||
| }; | ||
| const createPaymentsSpy = jest | ||
| .spyOn(service as any, 'createPayments') |
There was a problem hiding this comment.
[design]
Spying on private methods (using service as any) can make tests brittle and tightly coupled to the implementation details. Consider testing the public API of the service instead.
| userId: string; | ||
| type: WinningsCategory; | ||
| currency: string; | ||
| currency: PrizeType; |
There was a problem hiding this comment.
[❗❗ correctness]
The change from string to PrizeType for the currency field suggests a type change. Ensure that PrizeType is an appropriate type for representing currency values and that all usages of PaymentPayload are updated accordingly. If PrizeType is an enum or a complex type, verify that it aligns with expected currency formats and values.
| throw new Error("Challenge isn't in a payable status!"); | ||
| } | ||
|
|
||
| if (challenge.funChallenge === true) { |
There was a problem hiding this comment.
[correctness]
Consider using === instead of == for strict equality check. While funChallenge is likely a boolean, using strict equality ensures type safety and avoids potential issues if the type changes in the future.
| export interface ResourceRole { | ||
| id: string; | ||
| name: string; | ||
| fullWriteAccess?: boolean; |
There was a problem hiding this comment.
[correctness]
The addition of the fullWriteAccess property as an optional boolean in the ResourceRole interface could lead to potential issues if not handled properly elsewhere in the codebase. Ensure that all logic that checks or uses this property accounts for the possibility of it being undefined. Consider providing a default value or handling it explicitly in the code that uses this interface.
| }, | ||
| }; | ||
|
|
||
| winningsRepo = new WinningsRepository(prismaMock as any); |
There was a problem hiding this comment.
[maintainability]
Casting prismaMock as any can hide potential type errors. Consider defining a proper type for prismaMock to ensure type safety and maintainability.
| jest.useRealTimers(); | ||
| }); | ||
|
|
||
| async function getCreatedAtFilter(date: DateFilterType): Promise<any> { |
There was a problem hiding this comment.
[maintainability]
The function getCreatedAtFilter returns any, which can lead to type safety issues. Consider specifying a more precise return type to improve maintainability and readability.
| private generateFilterDate(date?: DateFilterType) { | ||
| let filterDate: object | undefined; | ||
| const currentDay = new Date(new Date().setHours(0, 0, 0, 0)); | ||
| const currentDayEnd = new Date(currentDay.getTime() + ONE_DAY - 1); |
There was a problem hiding this comment.
[❗❗ correctness]
The calculation of currentDayEnd using ONE_DAY - 1 assumes ONE_DAY is defined as the number of milliseconds in a day. Ensure ONE_DAY is correctly defined to avoid potential off-by-one errors or incorrect date calculations.
| private getOrderByWithWinnerId( | ||
| sortBy: string | undefined, | ||
| sortOrder: 'asc' | 'desc', | ||
| sortOrder: 'asc' | 'desc' | undefined, |
There was a problem hiding this comment.
[❗❗ correctness]
Allowing sortOrder to be undefined without handling it in the method logic could lead to unexpected behavior. Consider providing a default value or handling the undefined case explicitly within the method.
| const result = new ResponseDto<SearchWinningResult>(); | ||
| const includeCount = options.includeCount ?? true; | ||
| const includePayoutStatus = options.includePayoutStatus ?? true; | ||
| const latestPaymentOnly = options.latestPaymentOnly ?? false; |
There was a problem hiding this comment.
[correctness]
The newly introduced latestPaymentOnly variable is set to false by default. Ensure that this default behavior aligns with the intended functionality, as it may affect the logic where this variable is used.
…credited-payments PM-4165 allow editing credited payments
| created_at: 'desc', | ||
| }, | ||
| ], | ||
| take: latestPaymentOnly ? 1 : undefined, |
There was a problem hiding this comment.
[💡 readability]
The use of take: latestPaymentOnly ? 1 : undefined is technically correct, but consider explicitly setting take to null instead of undefined when latestPaymentOnly is false. This can improve clarity by making it explicit that no limit is intended, as undefined might imply an unintentional omission.
| import { isNumber, includes } from 'lodash'; | ||
| import { ENV_CONFIG } from 'src/config'; | ||
| import { ChallengeStatuses } from 'src/dto/challenge.dto'; | ||
| import { Logger } from 'src/shared/global'; |
There was a problem hiding this comment.
[❗❗ correctness]
The import path for Logger has been changed to src/shared/global. Ensure that this new path is correct and that the Logger implementation meets the requirements of the service. If the Logger is a custom implementation, verify that it provides the necessary functionality and is properly tested.
https://topcoder.atlassian.net/browse/PM-4026
https://topcoder.atlassian.net/browse/PM-4165
https://topcoder.atlassian.net/browse/PM-2557
https://topcoder.atlassian.net/browse/PM-1839