Problem
server/sockets/apps.js manages long-running, multi-step operations (app:update, app:standardize, app:deploy, detect:start, standardize:start) and tracks active operations via an in-memory Map (activeAppOperations:21).
Despite being the critical socket backbone for App Management, no dedicated test suite exists for server/sockets/apps.js. The only test that imports registerAppHandlers is server/services/updatePreflightParity.test.js, which only tests PortOS-specific preflight refusals.
Consequently, several core mechanisms have zero test coverage:
- Conflict detection (
findConflictingOperation:30-31): A duplicate update or standardize on the same appId, or on a different app record sharing the same repoPath, must be refused with { appId, duplicate: true, message } on app:update:error or app:standardize:error.
- Active operations buffer and client reconnect (
activeOperationsPayload:24-26, recordOperationStep:47-51, socket.on('app:operations:list')): Operations must sanitize internal fields (repoPath stripped), deduplicate step frames by step ID, and replay active operations on connect or demand.
- Failure handling during
app:update: When appUpdater.updateApp rejects, the socket handler must catch the error, emit app:update:error, log to the audit ledger via logAction('update', ..., success: false, failure.message), broadcast notifyAppsChanged('update', appId), and clean up via endAppOperation.
- Failure handling during
app:standardize: When pm2Standardizer.analyzeApp or applyStandardization fails, the error must be emitted, steps recorded, and endAppOperation executed in finally.
Trigger
An operator triggers multiple simultaneous update/standardize actions from the App Management UI, navigates away and re-mounts the page during an active update, or encounters a failure in appUpdater.updateApp.
Impact
A regression in findConflictingOperation would allow two concurrent git pull or standardize runs to mutate the same git repository concurrently, corrupting working trees or PM2 configurations. A regression in activeAppOperations causes in-flight progress cards to vanish or desync upon page reload. Because there is no unit test suite for server/sockets/apps.js, regressions in socket data validation, audit logging, or error emission go completely unnoticed in CI.
Fix
Create server/sockets/apps.test.js using a socket/io test harness (adapting the pattern from server/services/updatePreflightParity.test.js:69-82) to assert the complete contract of server/sockets/apps.js:
- Collision guard tests:
- Dispatch
app:update when an operation is already active for the same app ID -> emits app:update:error with duplicate: true.
- Dispatch
app:update for App B pointing to the same repoPath as active App A -> emits duplicate: true error.
- Dispatch
app:standardize while app:update is running on the same app -> emits duplicate: true.
- In-flight operations buffer tests:
- Dispatching an update emits
app:operations:active containing the active operation with repoPath excluded.
- Incoming step events update the buffer via
recordOperationStep.
- Emitting
app:operations:list returns the current active operations list.
- Failure and cleanup tests:
- When
appUpdater.updateApp rejects, verify logAction is called with success: false and the error message, notifyAppsChanged('update', appId) is called, and endAppOperation emits app:operations:active without the app.
- When
appStandardizeSchema validation fails or app is missing, appropriate error events are emitted.
Rejected alternative: Testing socket handlers exclusively through client-side React integration tests (client/src/pages/Apps.test.jsx) was rejected because client tests mock socket events rather than testing the server's Socket.IO event handler implementations.
Acceptance criteria
Problem
server/sockets/apps.jsmanages long-running, multi-step operations (app:update,app:standardize,app:deploy,detect:start,standardize:start) and tracks active operations via an in-memory Map (activeAppOperations:21).Despite being the critical socket backbone for App Management, no dedicated test suite exists for
server/sockets/apps.js. The only test that importsregisterAppHandlersisserver/services/updatePreflightParity.test.js, which only tests PortOS-specific preflight refusals.Consequently, several core mechanisms have zero test coverage:
findConflictingOperation:30-31): A duplicate update or standardize on the sameappId, or on a different app record sharing the samerepoPath, must be refused with{ appId, duplicate: true, message }onapp:update:errororapp:standardize:error.activeOperationsPayload:24-26,recordOperationStep:47-51,socket.on('app:operations:list')): Operations must sanitize internal fields (repoPathstripped), deduplicate step frames by step ID, and replay active operations on connect or demand.app:update: WhenappUpdater.updateApprejects, the socket handler must catch the error, emitapp:update:error, log to the audit ledger vialogAction('update', ..., success: false, failure.message), broadcastnotifyAppsChanged('update', appId), and clean up viaendAppOperation.app:standardize: Whenpm2Standardizer.analyzeApporapplyStandardizationfails, the error must be emitted, steps recorded, andendAppOperationexecuted infinally.Trigger
An operator triggers multiple simultaneous update/standardize actions from the App Management UI, navigates away and re-mounts the page during an active update, or encounters a failure in
appUpdater.updateApp.Impact
A regression in
findConflictingOperationwould allow two concurrentgit pullorstandardizeruns to mutate the same git repository concurrently, corrupting working trees or PM2 configurations. A regression inactiveAppOperationscauses in-flight progress cards to vanish or desync upon page reload. Because there is no unit test suite forserver/sockets/apps.js, regressions in socket data validation, audit logging, or error emission go completely unnoticed in CI.Fix
Create
server/sockets/apps.test.jsusing a socket/io test harness (adapting the pattern fromserver/services/updatePreflightParity.test.js:69-82) to assert the complete contract ofserver/sockets/apps.js:app:updatewhen an operation is already active for the same app ID -> emitsapp:update:errorwithduplicate: true.app:updatefor App B pointing to the samerepoPathas active App A -> emitsduplicate: trueerror.app:standardizewhileapp:updateis running on the same app -> emitsduplicate: true.app:operations:activecontaining the active operation withrepoPathexcluded.recordOperationStep.app:operations:listreturns the current active operations list.appUpdater.updateApprejects, verifylogActionis called withsuccess: falseand the error message,notifyAppsChanged('update', appId)is called, andendAppOperationemitsapp:operations:activewithout the app.appStandardizeSchemavalidation fails or app is missing, appropriate error events are emitted.Rejected alternative: Testing socket handlers exclusively through client-side React integration tests (
client/src/pages/Apps.test.jsx) was rejected because client tests mock socket events rather than testing the server's Socket.IO event handler implementations.Acceptance criteria
server/sockets/apps.test.jsis created and importsregisterAppHandlers.findConflictingOperationrejects concurrent operations on the same app ID or sharedrepoPathwithduplicate: true.app:operations:listand initial socket connection emitapp:operations:activewithrepoPathstripped.app:updatefailure logs tologActionwithsuccess: false, callsnotifyAppsChanged, and cleans up active operations.app:standardizeanalyze and apply errors emit structured error events and clean up active operations.cd server && npm test sockets/apps.test.jspasses deterministically.