feat: Add clone project template functionality - #367
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughProject templates can be cloned from the dashboard into configured projects. The backend applies template collections and authentication defaults, while common storage utilities and settings support Google Cloud Storage. ChangesProject template cloning
Google Cloud Storage support
Documentation verification
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Templates
participant DashboardAPI
participant MongoDB
Templates->>DashboardAPI: Submit templateId, name, and description
DashboardAPI->>DashboardAPI: Apply template collections and auth settings
DashboardAPI->>MongoDB: Save the new Project
MongoDB-->>DashboardAPI: Return created project
DashboardAPI-->>Templates: Return HTTP 201 and project data
Templates->>Templates: Navigate to the created project
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
apps/dashboard-api/src/controllers/project.controller.js (1)
271-333: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftUse the required API response envelope for project cloning.
The modified clone flow continues to return and consume raw project objects: the controller returns
projectObj, while the dashboard readsres.data._id. Standardize success responses as{ success, data, message }, useAppErrorfor errors, and navigate viares.data.data._id.
apps/dashboard-api/src/controllers/project.controller.js#L271-L333: return the prescribed success/error envelope fromcreateProject.apps/web-dashboard/src/pages/Templates.jsx#L187-L193: consume the project ID from the envelope’sdatafield.As per coding guidelines, “All endpoints must return
{ success: bool, data: {}, message: "" }; useAppErrorfor errors, never rawthrow, and never expose MongoDB errors to clients.”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 271 - 333, Update createProject in apps/dashboard-api/src/controllers/project.controller.js at lines 271-333 to return the standard { success, data, message } envelope, replace raw errors with AppError, and avoid exposing MongoDB errors; update the clone response handling in apps/web-dashboard/src/pages/Templates.jsx at lines 187-193 to read the project ID from res.data.data._id.Source: Coding guidelines
packages/common/src/utils/storage.manager.js (1)
354-369: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDo not require
endpointfor AWS S3.For
provider === "s3"without a custom endpoint,targetEndpointis undefined, sogetS3CompatibleStorage()rejects valid S3 configurations even though the dashboard S3 validation does not require an endpoint and the other S3/R2/GCS paths accept an undefined endpoint except where GCS’s default is set.Proposed fix
- if ( - !targetEndpoint || - !config.accessKeyId || - !config.secretAccessKey - ) { + if ( + (provider === "cloudflare_r2" && !targetEndpoint) || + !config.accessKeyId || + !config.secretAccessKey + ) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/common/src/utils/storage.manager.js` around lines 354 - 369, Update the validation condition in getS3CompatibleStorage so targetEndpoint is required only for cloudflare_r2, while preserving GCS’s default endpoint and allowing AWS S3 to use an undefined endpoint. Keep the existing accessKeyId and secretAccessKey requirements unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx`:
- Around line 31-33: Update the provider-change handling in the storage
configuration effect and the corresponding GCS initialization paths to reset
provider-specific values when switching to GCS. Clear or normalize storageUrl,
publicUrlHost, and any related endpoint state so stale R2 values cannot be
displayed, submitted, or take precedence over GCS defaults; preserve the
existing behavior for other providers.
In `@apps/web-dashboard/src/pages/Templates.jsx`:
- Around line 196-201: Update the clone flow around the finally block in
Templates.jsx so failed requests preserve the projectName and projectDescription
form values and keep the modal available for retry. Move field and modal cleanup
to the successful completion path or explicit cancellation handler, while
retaining isCloning reset for every outcome.
- Around line 439-487: Document the project-cloning workflow in both repository
documentation and the Templates dashboard near the Clone Modal rendered when
cloneTemplate is set. Explain the required projectName input, optional
projectDescription, and that cloning provisions the template’s authentication
setup and collections; keep the existing form behavior unchanged.
---
Outside diff comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 271-333: Update createProject in
apps/dashboard-api/src/controllers/project.controller.js at lines 271-333 to
return the standard { success, data, message } envelope, replace raw errors with
AppError, and avoid exposing MongoDB errors; update the clone response handling
in apps/web-dashboard/src/pages/Templates.jsx at lines 187-193 to read the
project ID from res.data.data._id.
In `@packages/common/src/utils/storage.manager.js`:
- Around line 354-369: Update the validation condition in getS3CompatibleStorage
so targetEndpoint is required only for cloudflare_r2, while preserving GCS’s
default endpoint and allowing AWS S3 to use an undefined endpoint. Keep the
existing accessKeyId and secretAccessKey requirements unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: af4857eb-ae87-4f0a-81ae-c16ca7a276d5
📒 Files selected for processing (9)
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.jsapps/dashboard-api/src/controllers/project.controller.jsapps/web-dashboard/src/components/Settings/StorageConfigForm.jsxapps/web-dashboard/src/pages/Templates.jsxmintlify/docs/docs.jsonpackages/common/src/index.jspackages/common/src/utils/input.validation.jspackages/common/src/utils/storage.manager.jspackages/common/src/utils/templates.js
…MongoDB fallback in createProject
…mode assertion in cloneTemplate test
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/dashboard-api/src/controllers/project.controller.js (1)
368-378: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winOnboarding-step reset logic has drifted between the primary and retry success paths, and the test suite can't catch it. The primary transaction path resets
collectionCreated/firstApiCallin addition to markingprojectCreated; the retry (non-transactional) path only marksprojectCreated. Because the test suite forcesmongoose.startSessionto always reject, the primary path — and this drift — is never exercised.
apps/dashboard-api/src/controllers/project.controller.js#L368-L378: extract the onboarding-mark + reset +emitEvent+ JSON-response logic into a shared helper used by both branches, so they can't diverge again.apps/dashboard-api/src/controllers/project.controller.js#L395-L401: call the same shared helper here instead of the abbreviated inline version that skips thecollectionCreated/firstApiCallreset.apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js#L49-L55: add a test scenario (or a separate suite) that exercises the primary transactional success path — e.g. by mockingmongoose.startSessionto resolve with a working session stub — so this success-handling logic is covered on both branches.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 368 - 378, Extract the shared onboarding success handling around markDeveloperOnboardingStep, subsequent-step resets, emitEvent, and the JSON response into a helper, then invoke it from both project.controller.js sites at lines 368-378 and 395-401 so retry and transactional paths behave identically. In project.controller.js lines 395-401, remove the abbreviated inline handling and use the helper. In apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js lines 49-55, add coverage with mongoose.startSession resolving to a working session stub to exercise the transactional success path alongside the retry path.
🧹 Nitpick comments (3)
apps/dashboard-api/src/controllers/project.controller.js (3)
310-312: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRemove leftover exploratory comments.
These read like in-progress debugging notes ("Wait, does 'users' collection get automatically added...") rather than documentation, and should be cleaned up before merge.
🧹 Proposed cleanup
if (template.isAuthEnabled) { newProject.isAuthEnabled = true; - // The default users collection is appended later or explicitly, let's see. - // Wait, does 'users' collection get automatically added if isAuthEnabled is set? - // Let's add it explicitly if not present. }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 310 - 312, Remove the leftover exploratory comments near the users collection handling in the project controller, including the notes about whether the default collection is appended automatically and adding it explicitly; leave the surrounding implementation unchanged.
315-327: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winReuse
getDefaultRlsForCollectioninstead of re-derivingownerFieldinline.This block reimplements RLS/ownerField derivation with a simpler rule than the existing
getDefaultRlsForCollection(lines 129-148), which already inspects the collection's own schema fields (userId/ownerId) and special-casesusers. The inline version here always falls back toownerField: 'userId'for any non-userscollection, ignoring anownerIdfield if the template collection has one. For the currentsdk-kanbantemplate this happens to match, but a future template collection modeled aroundownerIdwould silently get the wrongownerField, weakening the RLS enforcement for that collection.♻️ Proposed refactor
if (template.collections && template.collections.length > 0) { newProject.collections = template.collections.map(col => { const mode = typeof col.rls === 'string' ? col.rls : (col.rls?.mode || 'public-read'); + const defaults = getDefaultRlsForCollection(col.name, col.model); return { name: col.name, model: col.model, rls: { enabled: mode !== 'public-read', mode, - ownerField: col.name === 'users' ? '_id' : 'userId', + ownerField: defaults.ownerField, requireAuthForWrite: true } }; }); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 315 - 327, Update the template collection mapping to reuse getDefaultRlsForCollection for RLS configuration instead of deriving ownerField inline. Preserve the collection-specific mode handling where needed, while allowing the helper to inspect each collection schema, special-case users, and select userId or ownerId correctly.
387-393: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winUse structured error checking for transaction unsupported errors.
Broad substrings like
"standalone","Session", or"replica set"can match unrelatedcreateProjecterrors while still re-executing key generation, limit checks, and the save as a no-transaction project. Rely on the driver error shape/code for the transaction error, e.g."Transaction numbers are only allowed..."pluserr.codeName === 'IllegalOperation'/err.code === 20, so only unsupported MongoDB transactions trigger the fallback.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/controllers/project.controller.js` around lines 387 - 393, Update the transaction-error detection in the createProject error-handling path to rely on MongoDB’s structured error indicators rather than broad “standalone”, “Session”, or “replica set” message matches. Recognize the unsupported-transaction case only when the transaction-number message is present together with the appropriate driver codeName or code, such as IllegalOperation or 20, before re-executing the non-transaction fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 405-407: Update the error response logic using retryErr so only
AppError instances can provide a client-facing statusCode and message; remove
the fallback to retryErr.status from arbitrary errors. Treat all non-AppError
errors, including MongoDB or other internal errors, as status 500 with the
generic "Internal server error" message.
- Line 404: Update the error-response branches in the surrounding project
controller retry/error handling, including the ZodError and generic failure
paths, to consistently return the mandated { success, data, message } shape.
Replace error properties with data containing the existing issue details, and
add an appropriate empty data object to generic failures while preserving their
status codes and messages.
- Line 383: Update the abortTransaction error handler in the project
controller’s transaction cleanup flow to log the caught error instead of
silently swallowing it. Preserve the existing cleanup behavior while using the
controller’s established logging mechanism and include sufficient context about
the failed session abort.
---
Outside diff comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 368-378: Extract the shared onboarding success handling around
markDeveloperOnboardingStep, subsequent-step resets, emitEvent, and the JSON
response into a helper, then invoke it from both project.controller.js sites at
lines 368-378 and 395-401 so retry and transactional paths behave identically.
In project.controller.js lines 395-401, remove the abbreviated inline handling
and use the helper. In
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js lines
49-55, add coverage with mongoose.startSession resolving to a working session
stub to exercise the transactional success path alongside the retry path.
---
Nitpick comments:
In `@apps/dashboard-api/src/controllers/project.controller.js`:
- Around line 310-312: Remove the leftover exploratory comments near the users
collection handling in the project controller, including the notes about whether
the default collection is appended automatically and adding it explicitly; leave
the surrounding implementation unchanged.
- Around line 315-327: Update the template collection mapping to reuse
getDefaultRlsForCollection for RLS configuration instead of deriving ownerField
inline. Preserve the collection-specific mode handling where needed, while
allowing the helper to inspect each collection schema, special-case users, and
select userId or ownerId correctly.
- Around line 387-393: Update the transaction-error detection in the
createProject error-handling path to rely on MongoDB’s structured error
indicators rather than broad “standalone”, “Session”, or “replica set” message
matches. Recognize the unsupported-transaction case only when the
transaction-number message is present together with the appropriate driver
codeName or code, such as IllegalOperation or 20, before re-executing the
non-transaction fallback.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b9135c78-757e-40dc-9265-ab4dad6f2ba3
📒 Files selected for processing (6)
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.jsapps/dashboard-api/src/controllers/project.controller.jsapps/web-dashboard/src/components/Settings/StorageConfigForm.jsxapps/web-dashboard/src/pages/Templates.jsxmintlify/docs/docs.jsonpackages/common/src/utils/storage.manager.js
🚧 Files skipped from review as they are similar to previous changes (3)
- apps/web-dashboard/src/pages/Templates.jsx
- apps/web-dashboard/src/components/Settings/StorageConfigForm.jsx
- packages/common/src/utils/storage.manager.js
… proper mocks, fix AppError arg order & error handler status extraction - Rewrote cloneTemplate test to use complete @urbackend/common mock (no jest.requireActual) preventing Redis/BullMQ/GC side effects in CI - Fixed swapped AppError constructor args in createProject project-limit check (was: message, statusCode → now: statusCode, message) - Fixed error handler using AppError.status (string 'fail'/'error') instead of AppError.statusCode (numeric) for HTTP response - Added 4 new test cases: blank project, missing name validation, project limit enforcement, users collection verification - All 19 test suites, 164 tests pass
- Extracted finalizeProjectCreation helper for consistent success responses across transactional and retry paths
- Updated transaction error detection to strictly check MongoDB code 20 or IllegalOperation
- Fixed error response formats to consistently use { success, data, message } (replacing 'error' with 'data' and adding 'data: {}' for generic errors)
- Added error logging for abortTransaction failures
- Reused getDefaultRlsForCollection for template collections RLS setup
- Cleaned up exploratory comments
- Added test coverage for transactional success path
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js (2)
289-310: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winMissing negative-path coverage for transaction rollback.
This test only covers the commit/success path (
startTransaction→commitTransaction→endSession). Given the commit summary calls out fixing "transaction error detection increateProject," a companion test that forces a failure mid-transaction and assertsabortTransaction(notcommitTransaction) plusendSessionare called — and that the error response still follows the standard shape — would directly validate that fix.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js` around lines 289 - 310, Add a companion failure-path test beside the transactional success test for createProject that makes a mid-transaction operation reject, then assert abortTransaction and endSession are called, commitTransaction is not called, and the response uses the standard error shape/status. Reuse the existing req, res, and mockSession setup and restore any mocked failure behavior after the test.
249-255: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winError-path tests don't verify the standardized response body.
These tests only check
res.status, not that the JSON body follows the{ success, data, message }contract expected of all API endpoints, and that no raw/Mongo error details leak through. Given this PR's stated goal of fixingAppErrorhandling and error response formats increateProject, asserting the body shape here would directly validate that fix.As per coding guidelines: "All API endpoints must return
{ success: bool, data: {}, message: \"\" }; useAppErrorfor errors, never rawthrow, and never expose MongoDB errors to clients."✅ Suggested additional assertions
await createProject(req, res); expect(res.status).toHaveBeenCalledWith(400); + const responseObj = res.json.mock.calls[0][0]; + expect(responseObj.success).toBe(false); + expect(responseObj).toHaveProperty('message');Also applies to: 257-265
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js` around lines 249 - 255, Extend the error-path tests for createProject, including the missing-name case and the additional case around lines 257-265, to assert the JSON response follows the standardized { success, data, message } contract. Verify the error response indicates failure, uses an empty data object, provides the expected safe message, and does not expose raw MongoDB or internal error details, while retaining the existing 400 status assertions.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`:
- Around line 10-147: Replace the hand-duplicated PROJECT_TEMPLATES,
createProjectSchema, and AppError definitions with imports from their real
`@urbackend/common` submodules, and expose those imports through the
jest.mock('`@urbackend/common`') factory. Keep only side-effecting dependencies
mocked so the tests validate production template, schema, and error contracts.
---
Nitpick comments:
In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`:
- Around line 289-310: Add a companion failure-path test beside the
transactional success test for createProject that makes a mid-transaction
operation reject, then assert abortTransaction and endSession are called,
commitTransaction is not called, and the response uses the standard error
shape/status. Reuse the existing req, res, and mockSession setup and restore any
mocked failure behavior after the test.
- Around line 249-255: Extend the error-path tests for createProject, including
the missing-name case and the additional case around lines 257-265, to assert
the JSON response follows the standardized { success, data, message } contract.
Verify the error response indicates failure, uses an empty data object, provides
the expected safe message, and does not expose raw MongoDB or internal error
details, while retaining the existing 400 status assertions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: f9cac986-1c97-4233-9b27-957b613958e9
📒 Files selected for processing (2)
apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.jsapps/dashboard-api/src/controllers/project.controller.js
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/dashboard-api/src/controllers/project.controller.js
| const PROJECT_TEMPLATES = { | ||
| 'sdk-kanban': { | ||
| name: 'Kanban Board', | ||
| description: 'Full-featured Kanban board with drag-and-drop tasks.', | ||
| isAuthEnabled: true, | ||
| collections: [ | ||
| { | ||
| name: 'boards', | ||
| rls: 'private', | ||
| model: [{ key: 'title', type: 'String', required: true }], | ||
| }, | ||
| { | ||
| name: 'tasks', | ||
| rls: 'private', | ||
| model: [ | ||
| { key: 'boardId', type: 'Ref', required: true }, | ||
| { key: 'title', type: 'String', required: true }, | ||
| { key: 'status', type: 'String', required: true, default: 'todo' }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| }; | ||
|
|
||
| /* ------------------------------------------------------------------ */ | ||
| /* Real Zod — needed for schema validation inside createProject */ | ||
| /* ------------------------------------------------------------------ */ | ||
| const { z } = require('zod'); | ||
|
|
||
| /* Reconstruct the real createProjectSchema so parse() actually works */ | ||
| const createProjectSchema = z.object({ | ||
| name: z.string().min(1, 'Project name is required'), | ||
| description: z.string().optional(), | ||
| templateId: z.string().optional(), | ||
| siteUrl: z.preprocess( | ||
| (val) => (val === '' || val === null ? undefined : val), | ||
| z | ||
| .string() | ||
| .url('Invalid Site URL format') | ||
| .refine((url) => { | ||
| try { | ||
| const parsed = new URL(url); | ||
| return ( | ||
| parsed.protocol === 'https:' || | ||
| (parsed.protocol === 'http:' && | ||
| ['localhost', '127.0.0.1', '::1'].includes(parsed.hostname)) | ||
| ); | ||
| } catch { | ||
| return false; | ||
| } | ||
| }, 'Site URL must use HTTPS (or http://localhost for local development)') | ||
| .optional(), | ||
| ), | ||
| }); | ||
|
|
||
| /* ------------------------------------------------------------------ */ | ||
| /* Mock @urbackend/common — NO jest.requireActual to avoid side- */ | ||
| /* effects (Redis, BullMQ queues, GC timers, email service, etc.) */ | ||
| /* ------------------------------------------------------------------ */ | ||
| const mockSave = jest.fn(); | ||
| const mockCountDocuments = jest.fn(); | ||
|
|
||
| // Minimal Project constructor mock that behaves like a Mongoose model | ||
| function MockProject(data) { | ||
| Object.assign(this, data); | ||
| this._id = data._id || new mongoose.Types.ObjectId(); | ||
| this.collections = this.collections || []; | ||
| this.isAuthEnabled = this.isAuthEnabled || false; | ||
| } | ||
| MockProject.prototype.save = mockSave; | ||
| MockProject.prototype.toObject = function () { | ||
| const obj = { ...this }; | ||
| // Simulate toObject stripping prototype methods | ||
| delete obj.save; | ||
| delete obj.toObject; | ||
| return obj; | ||
| }; | ||
| MockProject.countDocuments = mockCountDocuments; | ||
| // For spying / instanceof checks | ||
| MockProject.schema = { obj: {} }; | ||
|
|
||
| jest.mock('@urbackend/common', () => ({ | ||
| Project: MockProject, | ||
| PROJECT_TEMPLATES, | ||
| createProjectSchema, | ||
| generateApiKey: jest.fn(() => 'test_api_key'), | ||
| hashApiKey: jest.fn(() => 'hashed_key'), | ||
| encrypt: jest.fn(() => ({ encrypted: 'enc', iv: 'iv', tag: 'tag' })), | ||
| markDeveloperOnboardingStep: jest.fn().mockResolvedValue(), | ||
| AppError: class AppError extends Error { | ||
| constructor(statusCode, message, error = null) { | ||
| super(message); | ||
| this.name = 'AppError'; | ||
| this.statusCode = statusCode; | ||
| this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; | ||
| this.error = error || (statusCode >= 500 ? 'Internal Server Error' : 'Error'); | ||
| this.isOperational = true; | ||
| } | ||
| }, | ||
| // Stubs for other imports used at module-level by project.controller.js | ||
| Developer: { findById: jest.fn() }, | ||
| Log: { aggregate: jest.fn() }, | ||
| getStorage: jest.fn(), | ||
| getConnection: jest.fn(), | ||
| getCompiledModel: jest.fn(), | ||
| QueryEngine: jest.fn(), | ||
| storageRegistry: {}, | ||
| webhookQueue: { add: jest.fn() }, | ||
| enqueueCollectionCleanup: jest.fn(), | ||
| syncCollectionCleanup: jest.fn(), | ||
| resolveEffectivePlan: jest.fn(() => ({ name: 'free' })), | ||
| deleteProjectByApiKeyCache: jest.fn(), | ||
| setProjectById: jest.fn(), | ||
| getProjectById: jest.fn(), | ||
| deleteProjectById: jest.fn(), | ||
| isProjectStorageExternal: jest.fn(() => false), | ||
| getBucket: jest.fn(() => 'default'), | ||
| getPresignedUploadUrl: jest.fn(), | ||
| verifyUploadedFile: jest.fn(), | ||
| getPublicIp: jest.fn(), | ||
| clearCompiledModel: jest.fn(), | ||
| createUniqueIndexes: jest.fn(), | ||
| ApiAnalytics: { aggregate: jest.fn() }, | ||
| MailLog: { aggregate: jest.fn() }, | ||
| getProjectAccessQuery: jest.fn((userId) => ({ | ||
| $or: [{ owner: userId }, { 'members.user': userId }], | ||
| })), | ||
| getProjectRole: jest.fn(), | ||
| Invitation: { find: jest.fn() }, | ||
| sanitizeObjectId: jest.fn((v) => v), | ||
| sanitizeNonEmptyString: jest.fn((v) => v), | ||
| createCollectionSchema: { parse: jest.fn((v) => v) }, | ||
| editCollectionSchema: { parse: jest.fn((v) => v) }, | ||
| updateExternalConfigSchema: { parse: jest.fn((v) => v) }, | ||
| updateAuthProvidersSchema: { parse: jest.fn((v) => v) }, | ||
| decrypt: jest.fn(() => 'decrypted'), | ||
| getS3CompatibleStorage: jest.fn(), | ||
| })); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Fully re-implemented mocks risk silent drift from production contracts.
PROJECT_TEMPLATES, createProjectSchema, and AppError are hand-duplicated here instead of pulled from @urbackend/common. If the real packages/common/src/utils/templates.js, input.validation.js, or the real AppError class change, these tests keep passing against stale copies while production behavior silently diverges — defeating the purpose of the "Template creation validation" coverage this PR adds.
Since the side-effecting parts live behind the package's index (Redis/BullMQ/GC timers), consider requiring the specific submodules directly instead of hand-copying their contents:
♻️ Suggested approach
-const PROJECT_TEMPLATES = {
- 'sdk-kanban': { ... },
-};
-const { z } = require('zod');
-const createProjectSchema = z.object({ ... });
+const { PROJECT_TEMPLATES } = require('`@urbackend/common/src/utils/templates`');
+const { createProjectSchema } = require('`@urbackend/common/src/utils/input.validation`');
+const { AppError } = require('`@urbackend/common/src/utils/errors`'); // adjust to actual pathThen reference these real exports inside the jest.mock('@urbackend/common', ...) factory instead of the local re-implementations, keeping only the heavy/side-effecting stubs (Redis, BullMQ, GCS, etc.) mocked.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/dashboard-api/src/__tests__/project.controller.cloneTemplate.test.js`
around lines 10 - 147, Replace the hand-duplicated PROJECT_TEMPLATES,
createProjectSchema, and AppError definitions with imports from their real
`@urbackend/common` submodules, and expose those imports through the
jest.mock('`@urbackend/common`') factory. Keep only side-effecting dependencies
mocked so the tests validate production template, schema, and error contracts.
Project Cloning feature
And Bring ur own GCS
Summary by CodeRabbit
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Documentation