126 lines
4.0 KiB
TypeScript
126 lines
4.0 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { FunctionService } from '../../src/services/functions/function.service.js';
|
|
|
|
const clientQueryMock = vi.fn();
|
|
const releaseMock = vi.fn();
|
|
|
|
const mockPool = {
|
|
query: vi.fn(),
|
|
connect: vi.fn().mockResolvedValue({
|
|
query: clientQueryMock,
|
|
release: releaseMock,
|
|
}),
|
|
};
|
|
|
|
vi.mock('../../src/infra/database/database.manager.js', () => ({
|
|
DatabaseManager: {
|
|
getInstance: () => ({
|
|
getPool: () => mockPool,
|
|
}),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../src/providers/functions/deno-subhosting.provider.js', () => ({
|
|
DenoSubhostingProvider: {
|
|
getInstance: () => ({
|
|
isConfigured: vi.fn().mockReturnValue(false),
|
|
}),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../src/services/secrets/secret.service.js', () => ({
|
|
SecretService: {
|
|
getInstance: () => ({}),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../src/utils/logger.js', () => ({
|
|
default: {
|
|
info: vi.fn(),
|
|
error: vi.fn(),
|
|
warn: vi.fn(),
|
|
debug: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
describe('FunctionService.deleteFunction — deployment cleanup', () => {
|
|
let service: FunctionService;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
service = FunctionService.getInstance();
|
|
});
|
|
|
|
it('removes the slug from deployment records inside a transaction', async () => {
|
|
clientQueryMock
|
|
.mockResolvedValueOnce({}) // BEGIN
|
|
.mockResolvedValueOnce({ rowCount: 1 }) // DELETE FROM functions.definitions
|
|
.mockResolvedValueOnce({ rowCount: 3 }) // UPDATE functions.deployments
|
|
.mockResolvedValueOnce({}); // COMMIT
|
|
|
|
const result = await service.deleteFunction('my-func');
|
|
|
|
expect(result).toBe(true);
|
|
|
|
const sqlCalls = clientQueryMock.mock.calls.map(([sql]) => sql as string);
|
|
expect(sqlCalls[0]).toBe('BEGIN');
|
|
expect(sqlCalls).toContainEqual(expect.stringContaining('DELETE FROM functions.definitions'));
|
|
expect(sqlCalls).toContainEqual(
|
|
expect.stringContaining('UPDATE functions.deployments SET functions = functions - $1')
|
|
);
|
|
expect(sqlCalls[sqlCalls.length - 1]).toBe('COMMIT');
|
|
|
|
// Verify the UPDATE uses correct parameters
|
|
const updateCall = clientQueryMock.mock.calls.find(
|
|
([sql]) => typeof sql === 'string' && sql.includes('UPDATE functions.deployments')
|
|
);
|
|
expect(updateCall?.[1]).toEqual(['my-func', JSON.stringify(['my-func'])]);
|
|
});
|
|
|
|
it('rolls back and returns false when function not found', async () => {
|
|
clientQueryMock
|
|
.mockResolvedValueOnce({}) // BEGIN
|
|
.mockResolvedValueOnce({ rowCount: 0 }) // DELETE — no match
|
|
.mockResolvedValueOnce({}); // ROLLBACK
|
|
|
|
const result = await service.deleteFunction('nonexistent');
|
|
|
|
expect(result).toBe(false);
|
|
|
|
const sqlCalls = clientQueryMock.mock.calls.map(([sql]) => sql as string);
|
|
expect(sqlCalls).toContain('ROLLBACK');
|
|
expect(sqlCalls).not.toContain('COMMIT');
|
|
});
|
|
|
|
it('rolls back on cleanup failure and releases the client with the error', async () => {
|
|
const cleanupError = new Error('connection lost');
|
|
|
|
clientQueryMock
|
|
.mockResolvedValueOnce({}) // BEGIN
|
|
.mockResolvedValueOnce({ rowCount: 1 }) // DELETE — success
|
|
.mockRejectedValueOnce(cleanupError) // UPDATE — fails
|
|
.mockResolvedValueOnce({}); // ROLLBACK
|
|
|
|
await expect(service.deleteFunction('my-func')).rejects.toBe(cleanupError);
|
|
|
|
const sqlCalls = clientQueryMock.mock.calls.map(([sql]) => sql as string);
|
|
expect(sqlCalls).toContain('ROLLBACK');
|
|
expect(sqlCalls).not.toContain('COMMIT');
|
|
expect(releaseMock).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('still triggers redeployment after successful cleanup', async () => {
|
|
clientQueryMock
|
|
.mockResolvedValueOnce({}) // BEGIN
|
|
.mockResolvedValueOnce({ rowCount: 1 }) // DELETE
|
|
.mockResolvedValueOnce({ rowCount: 0 }) // UPDATE (no deployments matched)
|
|
.mockResolvedValueOnce({}); // COMMIT
|
|
|
|
const scheduleSpy = vi.spyOn(service as never, 'scheduleDeployment' as never);
|
|
|
|
await service.deleteFunction('my-func');
|
|
|
|
expect(scheduleSpy).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|