Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions api/server/services/Files/process.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,17 @@ const createDeleteFileWithSecondaryStorage = ({ source, deleteFile, deletionMeth
);
}
if (hasCodeEnvRef(file) && source !== FileSources.execute_code) {
secondaryDeleteMethods.push(
getDeleteMethod({ source: FileSources.execute_code, deletionMethods }),
const deleteCodeEnvCopy = getDeleteMethod({
source: FileSources.execute_code,
deletionMethods,
});
/* The sandbox copy is a cache of the primary bytes and expires on its own. A Code API
* that refuses the delete (wrong profile, unreachable route) must not leave the user
* with a record they can never remove. */
secondaryDeleteMethods.push((req, file) =>
deleteCodeEnvCopy(req, file).catch((err) => {
logger.warn('Could not delete code environment copy; removing the file anyway', err);
}),
);
}

Expand Down
39 changes: 39 additions & 0 deletions api/server/services/Files/process.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2786,6 +2786,45 @@
expect(db.deleteFiles).toHaveBeenCalledWith(['code-resource-file']);
expect(result).toEqual({ deletedFileIds: ['code-resource-file'], failedFileIds: [] });
});

it('removes metadata and agent references when the code environment rejects the delete', async () => {
const primaryDelete = jest.fn().mockRejectedValue(

Check failure on line 2791 in api/server/services/Files/process.spec.js

View workflow job for this annotation

GitHub Actions / Static checks

Replace `.fn().mockRejectedValue(⏎······Object.assign(new·Error('no·such·file'),·{·code:·'ENOENT'·}),⏎····` with `⏎······.fn()⏎······.mockRejectedValue(Object.assign(new·Error('no·such·file'),·{·code:·'ENOENT'·})`
Object.assign(new Error('no such file'), { code: 'ENOENT' }),
);
const codeDelete = jest.fn().mockRejectedValue(
Object.assign(new Error('Request failed with status code 409'), {
response: { status: 409, data: { error: 'execution_profile_mismatch' } },
}),
);
getStrategyFunctions.mockImplementation((source) =>
source === FileSources.execute_code
? { deleteFile: codeDelete }
: { deleteFile: primaryDelete },
);
db.deleteFiles.mockResolvedValue({ deletedCount: 1 });
const req = {
body: {},
config: {},
user: { id: 'user-123', tenantId: 'tenant-a' },
};
const file = {
file_id: 'stale-code-file',
filepath: '/uploads/user-123/stale.pptx',
source: FileSources.local,
metadata: {
codeEnvRef: { kind: 'agent', id: 'agent-abc', storage_session_id: 's', file_id: 'f' },
},
};

const result = await processDeleteRequest({ req, files: [file] });

expect(codeDelete).toHaveBeenCalledWith(req, file);
expect(db.deleteFiles).toHaveBeenCalledWith(['stale-code-file']);
expect(db.removeAgentResourceFilesFromAllAgents).toHaveBeenCalledWith({
file_ids: ['stale-code-file'],
});
expect(result).toEqual({ deletedFileIds: ['stale-code-file'], failedFileIds: [] });
});
});

describe('sweepExpiredFiles', () => {
Expand Down
Loading