Conversation
…emplateId and attach default ai config when creating a challenge
…v6 into PM-4062_ai-config
[DO NOT MERGE] PM-4062 default ai config
PM-4062 fix migration file
…ing phase after the submission phase
| process.env.SUBMISSIONS_API_URL || "https://api.topcoder-dev.com/v5/submissions", | ||
| MEMBERS_API_URL: process.env.MEMBERS_API_URL || "https://api.topcoder-dev.com/v6/members", | ||
| REVIEW_SUMMATIONS_API_URL: process.env.REVIEW_SUMMATIONS_API_URL || "https://api.topcoder-dev.com/v6/reviewSummations", | ||
| REVIEWS_API_URL: process.env.REVIEWS_API_URL || "https://api.topcoder-dev.com", |
There was a problem hiding this comment.
[maintainability]
The REVIEWS_API_URL is set to https://api.topcoder-dev.com, which is inconsistent with the other API URLs that specify a version (e.g., /v5 or /v6). Consider adding a version to ensure consistency and avoid potential issues with future API changes.
| @@ -4,5 +4,6 @@ AUTH0_URL=https://topcoder-dev.auth0.com/oauth/token | |||
| AUTH0_AUDIENCE=https://m2m.topcoder-dev.com/ | |||
| DATABASE_URL="postgresql://topcoder:NUDvFEZzrey2x2og2!zn_kBzcaJ.fu_njAJcD*z2q@topcoder-services.ci8xwsszszsw.us-east-1.rds.amazonaws.com:5432/topcoder-services?schema=challenges" | |||
There was a problem hiding this comment.
[❗❗ security]
The DATABASE_URL contains hardcoded credentials, which is a security risk. Consider using environment variables or a secure vault to manage sensitive information.
PM-3926 ai screening phase
| '2025-03-10T13:08:02.378Z', | ||
| 'topcoder user' | ||
| ) | ||
| ON CONFLICT DO NOTHING; |
There was a problem hiding this comment.
[correctness]
Using ON CONFLICT DO NOTHING can silently ignore conflicts without logging or handling them, which might lead to data inconsistencies or missed updates. Consider specifying a conflict target and handling the conflict appropriately to ensure data integrity.
| * @param {Function} logDebugMessage optional logging function | ||
| */ | ||
| async addAIScreeningPhaseForChallengeCreation(challenge, prisma, logDebugMessage = () => {}) { | ||
| if (!challenge || !challenge.phases || !Array.isArray(challenge.reviewers)) { |
There was a problem hiding this comment.
[maintainability]
The function addAIScreeningPhaseForChallengeCreation mutates the challenge object in-place. Consider returning a new object or using a more functional approach to avoid potential side effects, which can improve maintainability.
| ); | ||
|
|
||
| if (!submissionPhaseName) { | ||
| throw new errors.BadRequestError( |
There was a problem hiding this comment.
[💡 maintainability]
Throwing an error when no submission phase is found is correct, but consider logging this event before throwing to aid in debugging and tracing issues in production.
| ([_, phase]) => phase.name === "AI Screening" | ||
| ); | ||
|
|
||
| if (!aiScreeningPhaseDefEntry) { |
There was a problem hiding this comment.
[💡 maintainability]
The error message 'AI Screening phase definition not found in the system' could be more informative by including the context or identifier of the challenge to help with debugging.
src/common/challenge-helper.js
Outdated
|
|
||
| // Recalculate phase dates to keep timeline in sync | ||
| if (submissionPhase.scheduledEndDate) { | ||
| aiScreeningPhase.scheduledStartDate = submissionPhase.scheduledEndDate; |
There was a problem hiding this comment.
[💡 performance]
The use of moment for date manipulation is fine, but consider using native JavaScript Date methods or a lighter library like date-fns if moment's full functionality is not needed, to reduce bundle size.
| @@ -98,9 +100,27 @@ class ChallengePhaseHelper { | |||
| // to incorrectly push earlier phases forward. Sorting by template order prevents that. | |||
| const orderIndex = new Map(); | |||
There was a problem hiding this comment.
[💡 performance]
The variable orderIndex is created using new Map() but is populated using _.each. Consider using native JavaScript methods like forEach for consistency and potentially better performance.
| const submissionPhase = submissionPhaseName | ||
| ? _.find(challengePhases, (phase) => phase.name === submissionPhaseName) | ||
| : null; | ||
| const submissionOrderIndex = _.isNil(submissionPhase) |
There was a problem hiding this comment.
[💡 readability]
The use of _.isNil to check for null or undefined is correct, but consider using submissionPhase === null for clarity since submissionPhase is explicitly set to null when not found.
| return updatedPhase; | ||
| }); | ||
|
|
||
| const aiScreeningPhase = _.find(updatedPhases, (phase) => phase.name === "AI Screening"); |
There was a problem hiding this comment.
[correctness]
The logic for updating the predecessor of aiScreeningPhase and review phases assumes that updateSubmissionPhase is not null. Ensure that this logic is covered by tests to prevent potential issues if updateSubmissionPhase is unexpectedly null.
PM-3926 - create the ai screening phase when challenge is launched
.github/workflows/trivy.yaml
Outdated
|
|
||
| - name: Run Trivy scanner in repo mode | ||
| uses: aquasecurity/trivy-action@0.33.1 | ||
| uses: aquasecurity/trivy-action@latest |
There was a problem hiding this comment.
[maintainability]
Using @latest for the Trivy action can lead to unexpected behavior if a breaking change is introduced in future versions. Consider specifying a specific version to ensure consistent behavior.
src/common/challenge-helper.js
Outdated
| // Recalculate phase dates to keep timeline in sync | ||
| if (submissionPhase.scheduledEndDate) { | ||
| aiScreeningPhase.scheduledStartDate = submissionPhase.scheduledEndDate; | ||
| aiScreeningPhase.scheduledEndDate = moment(aiScreeningPhase.scheduledStartDate) | ||
| .add(aiScreeningPhase.duration, "seconds") | ||
| .toDate() | ||
| .toISOString(); | ||
|
|
||
| logDebugMessage( | ||
| `AI screening phase dates calculated (start=${aiScreeningPhase.scheduledStartDate}, end=${aiScreeningPhase.scheduledEndDate})` | ||
| ); | ||
|
|
||
| // Update dates for review phases that now depend on AI Screening | ||
| reviewPhases.forEach((phase) => { | ||
| if (_.isNil(phase.actualStartDate)) { | ||
| phase.scheduledStartDate = aiScreeningPhase.scheduledEndDate; | ||
| if (phase.duration) { | ||
| phase.scheduledEndDate = moment(phase.scheduledStartDate) | ||
| .add(phase.duration, "seconds") | ||
| .toDate() | ||
| .toISOString(); | ||
|
|
||
| logDebugMessage( | ||
| `Updated ${phase.name} phase dates (start=${phase.scheduledStartDate}, end=${phase.scheduledEndDate})` | ||
| ); | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
🔴 Incomplete phase date cascade after AI Screening insertion causes stale downstream dates
When addAIScreeningPhaseForChallengeCreation inserts the AI Screening phase during challenge activation (src/services/ChallengeService.js:2725-2737), it only recalculates dates for the AI Screening phase itself and its immediate successor review phases (lines 624-638). Downstream phases whose predecessor chain passes through a review phase (e.g., Appeals → depends on Review, Appeals Response → depends on Appeals, Post-Mortem, etc.) retain their stale scheduledStartDate/scheduledEndDate values that were computed by populatePhasesForChallengeUpdate before the AI Screening was inserted. This means those downstream phases will have dates that overlap with or precede the shifted review phase, producing an incorrect timeline that is persisted via syncChallengePhases (src/services/ChallengeService.js:3031-3038).
Example of the stale date problem
Before AI Screening insertion (dates from populatePhasesForChallengeUpdate):
- Submission: ends T+3d
- Review: starts T+3d, ends T+6d
- Appeals: starts T+6d, ends T+7d
After AI Screening insertion (only Review shifted):
- Submission: ends T+3d
- AI Screening: starts T+3d, ends T+3d+4h
- Review: starts T+3d+4h, ends T+6d+4h ← updated
- Appeals: starts T+6d, ends T+7d ← NOT updated, overlaps with Review
Was this helpful? React with 👍 or 👎 to provide feedback.
| if (Array.isArray(challenge.reviewers) && challenge.reviewers.length > 0) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
🟡 Duplicate early-return guard check in applyDefaultMemberReviewersForChallengeCreation
The exact same guard check if (Array.isArray(challenge.reviewers) && challenge.reviewers.length > 0) { return; } appears twice in succession at lines 237-239 and 241-243. The second check is dead code that can never be reached (if the first check passes, the function already returned). This is harmless but suggests a copy-paste error.
| if (Array.isArray(challenge.reviewers) && challenge.reviewers.length > 0) { | |
| return; | |
| } |
Was this helpful? React with 👍 or 👎 to provide feedback.
| updatedBy String | ||
|
|
||
| @@index([abbreviation]) | ||
| @@index([legacyId]) |
There was a problem hiding this comment.
[performance]
Indexing on legacyId and isLegacy might not be necessary if these fields are not frequently queried. Consider evaluating the query patterns to ensure these indexes are justified, as unnecessary indexes can impact write performance.
| updatedBy String | ||
|
|
||
| @@index([legacyId]) | ||
| @@index([isLegacy]) |
There was a problem hiding this comment.
[performance]
Similar to the ChallengeType model, ensure that indexing on isLegacy is necessary based on query patterns. Unnecessary indexes can degrade performance by increasing the time taken for write operations.
PM-4199 - Review context generation -> dev
| process.env.REVIEW_SUMMATIONS_API_URL || "https://api.topcoder-dev.com/v6/reviewSummations", | ||
| REVIEWS_API_URL: process.env.REVIEWS_API_URL || "https://api.topcoder-dev.com", | ||
| TC_AI_API_URL: process.env.TC_AI_API_URL || "https://api.topcoder-dev.com/v6/ai", | ||
| RESOURCES_API_URL: process.env.RESOURCES_API_URL || "http://localhost:4000/v5/resources", |
There was a problem hiding this comment.
[❗❗ correctness]
The RESOURCES_API_URL is set to http://localhost:4000/v5/resources, which might be intended for local development. Ensure this is not mistakenly included in a production configuration.
| */ | ||
| function triggerChallengeReviewContextGeneration(challengeId) { | ||
| if (isGenerationInProgress(challengeId)) { | ||
| const existingRunId = getRunId(challengeId); |
There was a problem hiding this comment.
[💡 performance]
The function isGenerationInProgress is called twice for the same challengeId within this function. Consider storing the result in a variable to avoid redundant calls and improve performance.
|
|
||
| const cancelUrl = `${tcAiApiUrl}/workflows/${workflowId}/runs/${runId}/cancel`; | ||
| await axios.post( | ||
| cancelUrl, |
There was a problem hiding this comment.
[correctness]
Consider handling potential errors from the axios.post call to cancelUrl. Currently, if this request fails, it will not be caught, which could lead to unhandled promise rejections.
| while (Date.now() - startTime < WORKFLOW_POLL_TIMEOUT_MS) { | ||
| try { | ||
| await sleep(WORKFLOW_POLL_INTERVAL_MS); | ||
| const statusResponse = await axios.get(workflowStatusUrl, { |
There was a problem hiding this comment.
[correctness]
The axios.get call within the polling loop does not handle non-2xx status codes explicitly. Consider adding a validateStatus function to handle unexpected status codes gracefully.
| `[ChallengeReviewContextHelper] Polling error for workflow ${runId}: ${pollErr.message}`, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
[maintainability]
The polling loop does not have a mechanism to break out if the axios.get call consistently fails due to network issues. Consider adding a retry limit to avoid an infinite loop in case of persistent network failures.
| let existingContext = null; | ||
|
|
||
| try { | ||
| const getResponse = await axios.get(contextGetUrl, { |
There was a problem hiding this comment.
[correctness]
The axios.get call for checking existing context uses a validateStatus function that allows all status codes below 500. Consider narrowing this range to handle unexpected status codes more explicitly.
| * @param {string} runId - The workflow run ID (optional, can be set later) | ||
| */ | ||
| function markGenerationStarted(challengeId, runId = null) { | ||
| generationCache.set(challengeId, { timestamp: Date.now(), runId }); |
There was a problem hiding this comment.
[correctness]
Consider adding a check to ensure challengeId is not null or undefined before setting it in the generationCache. This will prevent potential issues with invalid keys in the cache.
| * @param {string} runId - The workflow run ID | ||
| */ | ||
| function setRunId(challengeId, runId) { | ||
| const entry = generationCache.get(challengeId); |
There was a problem hiding this comment.
[💡 maintainability]
It might be beneficial to handle the case where challengeId is not found in the generationCache by logging a warning or error. This can help in debugging scenarios where setRunId is called with an invalid challengeId.
| clearGeneration(challengeId); | ||
| } | ||
|
|
||
| markGenerationStarted(challengeId); | ||
| logger.info( | ||
| `[ChallengeReviewContextHelper] Triggering context generation for challenge ${challengeId}`, | ||
| ); | ||
|
|
||
| runReviewContextGenerationAsync(challengeId).catch((err) => { | ||
| logger.error( | ||
| `[ChallengeReviewContextHelper] Context generation failed for challenge ${challengeId}: ${err.message}`, | ||
| err, | ||
| ); | ||
| clearGeneration(challengeId); | ||
| }); |
There was a problem hiding this comment.
🟡 Old async run's clearGeneration call will erase the new run's guard entry
In triggerChallengeReviewContextGeneration, when a new generation is triggered while one is already in progress, the old runReviewContextGenerationAsync promise remains alive in the background. After clearGeneration(challengeId) at line 61 removes the old entry and markGenerationStarted(challengeId) at line 64 creates a new entry for the replacement run, the old run's promise will eventually complete (succeed, fail, or timeout) and call clearGeneration(challengeId) (at src/common/challenge-review-context-helper.js:74, :225, or :283), which deletes the new run's guard entry. This allows a subsequent update to start yet another concurrent generation while the replacement run is still active, defeating the deduplication guard.
Prompt for agents
In src/common/challenge-review-context-helper.js, the race condition occurs because the old runReviewContextGenerationAsync promise's completion handler calls clearGeneration(challengeId), which wipes the new run's guard entry. To fix this, add a generation counter or nonce to the guard so that clearGeneration only clears the entry if it matches the current generation. In review-context-generation-guard.js, change the generationCache to store a monotonically increasing nonce alongside the timestamp and runId. markGenerationStarted should return the nonce. clearGeneration should accept a nonce parameter and only delete the entry if the stored nonce matches the passed nonce. Then in challenge-review-context-helper.js, capture the nonce returned by markGenerationStarted (line 64) and pass it through to runReviewContextGenerationAsync so that its clearGeneration calls use the correct nonce. This ensures the old run's cleanup cannot interfere with the new run's guard.
Was this helpful? React with 👍 or 👎 to provide feedback.
| } | ||
| } catch (saveErr) { | ||
| logger.error( | ||
| `[ChallengeReviewContextHelper] Failed to save context for challenge ${challengeId}: ${JSON.stringify(saveErr, null, 2)}`, |
There was a problem hiding this comment.
[❗❗ security]
Using JSON.stringify to log errors can expose sensitive information if the error object contains any. Consider logging only the necessary parts of the error message to avoid potential security risks.
| let token; | ||
| try { | ||
| token = await m2mHelper.getM2MToken(); | ||
| } catch (tokenErr) { | ||
| logger.error( | ||
| `[ChallengeReviewContextHelper] Failed to get M2M token for challenge ${challengeId}: ${tokenErr.message}`, | ||
| ); | ||
| clearGeneration(challengeId); | ||
| return; | ||
| } | ||
|
|
||
| const authHeader = { Authorization: `Bearer ${token}` }; |
There was a problem hiding this comment.
🟡 Stale M2M token used across 15+ minute polling window causes silent context save failure
In runReviewContextGenerationAsync, a single M2M token is obtained at line 119 and stored in authHeader. This same token is then reused across the entire workflow: creating a run, polling for up to 15 minutes (WORKFLOW_POLL_TIMEOUT_MS), and finally saving the context to the review API (lines 238-274). If the M2M token expires during the long polling period, all subsequent API calls (including polling and the critical final context save) will fail. During polling, expired-token errors are silently caught as network errors (lines 210-218) and polling continues fruitlessly until timeout. The workflow result is then lost even if the workflow completed successfully on the server side.
Prompt for agents
In src/common/challenge-review-context-helper.js, the runReviewContextGenerationAsync function obtains a single M2M token at line 119 and stores it in authHeader at line 128. This token is reused for all subsequent API calls including a polling loop that can last up to 15 minutes. Re-fetch the M2M token before critical API calls: (1) before the polling loop or at least periodically within it, and (2) before the final context save (around line 238). You can do this by calling m2mHelper.getM2MToken() again to get a fresh/refreshed token and rebuilding the authHeader object.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
Pull request overview
This PR packages the March 2026 production release changes for projects-api-v6/challenge-api-v6, focusing on legacy track/type visibility, AI-review-driven “AI Screening” phase behavior, and updated access controls/config wiring.
Changes:
- Add
legacyId/isLegacyflags for challenge types/tracks (schema, migrations, seeds, search filters, and unit tests). - Introduce AI Screening phase seeding and enforce AI-review completion checks before closing AI Screening / progressing phases; shift successor schedules on manual close.
- Refactor default reviewer AI settings to use
aiConfigTemplateIdand integrate Reviews API config creation; expand challenge edit route access to include Talent Manager.
Reviewed changes
Copilot reviewed 33 out of 37 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/unit/ChallengeTypeService.legacy.test.js | Adds unit coverage for default legacy filtering and legacy metadata on type creation. |
| test/unit/ChallengeTrackService.test.js | Updates tests for track aliasing plus legacy filter behavior. |
| test/unit/ChallengeService.test.js | Adds tests for default AI config application when reviewers aren’t provided. |
| test/unit/ChallengeRoutes.test.js | Verifies Talent Manager role has access to challenge create/edit routes. |
| test/unit/ChallengePhaseService.test.js | Adds test ensuring successor schedules shift to actual end time when closing a phase. |
| test/testHelper.js | Updates seeded track enum value to canonical DEVELOPMENT. |
| src/services/DefaultChallengeReviewerService.js | Renames default AI reviewer field to aiConfigTemplateId in payload normalization and Joi schemas. |
| src/services/ChallengeTypeService.js | Adds legacyId + isLegacy support (search/filtering, validation, uniqueness checks). |
| src/services/ChallengeTrackService.js | Adds isLegacy filtering and schema support; normalizes track aliases. |
| src/services/ChallengeService.js | Adds AI Screening closure guard, default AI config application, and AI review config creation callouts. |
| src/services/ChallengePhaseService.js | Adds escalation checks, AI Screening close validation, and successor schedule shifting logic. |
| src/scripts/seed/Phase.json | Seeds the new AI Screening phase definition. |
| src/scripts/seed/ChallengeType.json | Seeds legacy challenge types and adds isLegacy/legacyId fields. |
| src/scripts/seed/ChallengeTrack.json | Normalizes track enum values and seeds isLegacy. |
| src/routes.js | Refactors repeated access role lists and adds Talent Manager to challenge editor routes. |
| src/common/project-helper.js | Requests members explicitly from projects-api-v6. |
| src/common/phase-helper.js | Adjusts ordering and predecessor wiring for AI Screening in phase updates. |
| src/common/helper.js | Adds getAIReviewConfigByChallengeId via Reviews API. |
| src/common/challenge-helper.js | Adds default reviewer application helpers and AI Screening phase injection + AI config/template integration. |
| prisma/schema.prisma | Adds legacyId/isLegacy to types, isLegacy to tracks; renames DefaultChallengeReviewer field. |
| prisma/migrations/20260316090000_add_legacy_type_visibility_flags/migration.sql | Migration for legacy visibility flags + seed insertions. |
| prisma/migrations/20260306100000_add_ai_screening_phase/migration.sql | Migration to insert the AI Screening phase definition. |
| prisma/migrations/20260225100000_rename_default_ai_workflow_id/migration.sql | Renames DefaultChallengeReviewer column to aiConfigTemplateId. |
| packages/challenge-prisma-client/wasm.js | Regenerates prisma client artifacts reflecting schema updates. |
| packages/challenge-prisma-client/schema.prisma | Updates generated prisma schema (legacy fields, isLegacy, funChallenge, indices). |
| packages/challenge-prisma-client/package.json | Updates generated prisma client package name/hash. |
| packages/challenge-prisma-client/index-browser.js | Updates browser prisma client artifacts reflecting schema updates. |
| docs/swagger.yaml | Documents new query params/fields for legacy filtering and track/type legacy metadata. |
| docs/prod.env | Adds REVIEWS_API_URL env var to production env template. |
| docs/dev.env | Adds REVIEWS_API_URL env var for dev template. |
| config/default.js | Adds REVIEWS_API_URL and TC_AI_API_URL configuration defaults; formatting cleanup. |
| app-constants.js | Adds TalentManager role constant. |
| .github/workflows/trivy.yaml | Bumps Trivy action version. |
| .circleci/config.yml | Updates CircleCI branch filters for workflows. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| await challengeHelper.createAIReviewConfigsForChallengeCreation( | ||
| ret.id, | ||
| aiReviewConfigsForCreation, | ||
| debugLog, | ||
| ); |
There was a problem hiding this comment.
createAIReviewConfigsForChallengeCreation is invoked after the challenge DB record is created. If the reviews API call fails (network/4xx/5xx), createChallenge will throw but the challenge will already exist, which can lead to orphaned records and non-idempotent retries. Consider wrapping challenge creation + AI config creation in a transaction/compensation flow (e.g., delete the challenge on failure), or treat AI config creation as best-effort (log + continue) and reconcile asynchronously.
| await challengeHelper.createAIReviewConfigsForChallengeCreation( | |
| ret.id, | |
| aiReviewConfigsForCreation, | |
| debugLog, | |
| ); | |
| try { | |
| await challengeHelper.createAIReviewConfigsForChallengeCreation( | |
| ret.id, | |
| aiReviewConfigsForCreation, | |
| debugLog, | |
| ); | |
| } catch (err) { | |
| logger.error( | |
| `createChallenge: failed to create AI review configs for challengeId=${ret.id} ${buildLogContext()}: ${err.message}`, | |
| ); | |
| logger.debug(err.stack); | |
| } |
…g-phase-when-editing-after-launch PM-4478 add ai screening phase when editing after launch
…g-phase-when-editing-after-launch PM-4478 add ai screening phase when editing after launch
…hallenges from project-api-v6
| ('f9570d73-4f26-4d3a-b804-7f6f2f4b85e9', 'BUG_HUNT', 'Legacy develop subtype retained for historical stats references.', 'LBGH', 120), | ||
| ('65d2d4d4-a0e2-4f5d-94df-570f0b084439', 'CONTENT_CREATION', 'Legacy develop subtype retained for historical stats references.', 'LCTC', 146) | ||
| ) AS seed("id", "name", "description", "abbreviation", "legacyId") | ||
| WHERE NOT EXISTS ( |
There was a problem hiding this comment.
[correctness]
The WHERE NOT EXISTS clause checks for existing entries by name or legacyId. Ensure that this logic aligns with the intended uniqueness constraints of the ChallengeType table. If both fields are meant to be unique, this is correct; otherwise, consider revising to prevent unintended data exclusion.
| return null; | ||
| } | ||
|
|
||
| const markup = _.toNumber(rawMarkup); |
There was a problem hiding this comment.
[correctness]
Consider using Number(rawMarkup) instead of _.toNumber(rawMarkup) for converting to a number. Lodash's toNumber is generally used for broader type coercion, which might not be necessary here and could introduce unexpected behavior if rawMarkup is not a string or number.
| } | ||
|
|
||
| const markup = _.toNumber(rawMarkup); | ||
| if (!Number.isFinite(markup)) { |
There was a problem hiding this comment.
[correctness]
The check !Number.isFinite(markup) might not catch all invalid cases if markup is NaN. Consider using Number.isNaN(markup) to explicitly handle NaN values.
| }) | ||
|
|
||
| it('preserves decimal billing markup returned by projects api', async () => { | ||
| m2mHelper.getM2MToken = async () => 'test-token' |
There was a problem hiding this comment.
[maintainability]
Overriding m2mHelper.getM2MToken directly in the test can lead to side effects if other tests rely on the original implementation. Consider using a mocking library like sinon to stub this method instead.
|
|
||
| it('preserves decimal billing markup returned by projects api', async () => { | ||
| m2mHelper.getM2MToken = async () => 'test-token' | ||
| axios.get = async () => ({ |
There was a problem hiding this comment.
[maintainability]
Overriding axios.get directly in the test can lead to side effects if other tests rely on the original implementation. Consider using a mocking library like nock or sinon to stub this method instead.
| }) | ||
|
|
||
| it('converts legacy whole-percentage billing markup to decimal format', async () => { | ||
| m2mHelper.getM2MToken = async () => 'test-token' |
There was a problem hiding this comment.
[maintainability]
Overriding m2mHelper.getM2MToken directly in the test can lead to side effects if other tests rely on the original implementation. Consider using a mocking library like sinon to stub this method instead.
|
|
||
| it('converts legacy whole-percentage billing markup to decimal format', async () => { | ||
| m2mHelper.getM2MToken = async () => 'test-token' | ||
| axios.get = async () => ({ |
There was a problem hiding this comment.
[maintainability]
Overriding axios.get directly in the test can lead to side effects if other tests rely on the original implementation. Consider using a mocking library like nock or sinon to stub this method instead.
Uh oh!
There was an error while loading. Please reload this page.