PSA/packages/storage/tests/storageService.workflowEvents.test.ts
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

166 lines
5.5 KiB
TypeScript

import { describe, expect, it, vi, beforeEach } from 'vitest';
vi.mock('@alga-psa/db', () => ({
createTenantKnex: vi.fn(),
}));
vi.mock('@alga-psa/event-bus/publishers', () => ({
publishWorkflowEvent: vi.fn(),
}));
vi.mock('../src/config/storage', () => ({
getProviderConfig: vi.fn(),
getStorageConfig: vi.fn(),
validateFileUpload: vi.fn(async () => {}),
}));
vi.mock('../src/StorageProviderFactory', () => ({
StorageProviderFactory: {
createProvider: vi.fn(),
},
generateStoragePath: vi.fn(() => 'tenant-1/files/sample.txt'),
}));
vi.mock('../src/models/storage', () => ({
FileStoreModel: {
create: vi.fn(),
findById: vi.fn(),
softDelete: vi.fn(),
},
}));
vi.mock('@alga-psa/validation', () => ({
isValidUUID: vi.fn(() => true),
}));
vi.mock('file-type', () => ({
fileTypeFromBuffer: vi.fn(async () => undefined),
}));
import { createTenantKnex } from '@alga-psa/db';
import { publishWorkflowEvent } from '@alga-psa/event-bus/publishers';
import { StorageProviderFactory } from '../src/StorageProviderFactory';
import { getProviderConfig, getStorageConfig } from '../src/config/storage';
import { FileStoreModel } from '../src/models/storage';
import { StorageService } from '../src/StorageService';
describe('StorageService.uploadFile workflow events', () => {
const createTenantKnexMock = vi.mocked(createTenantKnex);
const publishWorkflowEventMock = vi.mocked(publishWorkflowEvent);
const createProviderMock = vi.mocked(StorageProviderFactory.createProvider);
const getStorageConfigMock = vi.mocked(getStorageConfig);
const getProviderConfigMock = vi.mocked(getProviderConfig);
const fileCreateMock = vi.mocked(FileStoreModel.create);
const fileFindByIdMock = vi.mocked(FileStoreModel.findById);
const fileSoftDeleteMock = vi.mocked(FileStoreModel.softDelete);
beforeEach(() => {
publishWorkflowEventMock.mockReset();
createProviderMock.mockReset();
getStorageConfigMock.mockReset();
getProviderConfigMock.mockReset();
fileCreateMock.mockReset();
fileFindByIdMock.mockReset();
fileSoftDeleteMock.mockReset();
createTenantKnexMock.mockReset();
});
it('publishes FILE_UPLOADED after creating the file record', async () => {
createProviderMock.mockResolvedValue({
upload: vi.fn(async () => ({ path: 'tenant-1/files/sample.txt' })),
} as any);
createTenantKnexMock.mockResolvedValue({ knex: {} } as any);
fileCreateMock.mockResolvedValue({
file_id: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
original_name: 'sample.txt',
mime_type: 'text/plain',
file_size: 3,
storage_path: 'tenant-1/files/sample.txt',
uploaded_by_id: 'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0',
created_at: '2026-01-24T12:00:00.000Z',
} as any);
await StorageService.uploadFile('tenant-1', Buffer.from('abc'), 'sample.txt', {
mime_type: 'text/plain',
uploaded_by_id: 'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0',
});
expect(publishWorkflowEventMock).toHaveBeenCalledWith(
expect.objectContaining({
eventType: 'DOCUMENT_UPLOADED',
payload: expect.objectContaining({
documentId: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
uploadedByUserId: 'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0',
uploadedAt: '2026-01-24T12:00:00.000Z',
fileName: 'sample.txt',
contentType: 'text/plain',
sizeBytes: 3,
storageKey: 'tenant-1/files/sample.txt',
}),
ctx: expect.objectContaining({ tenantId: 'tenant-1' }),
})
);
expect(publishWorkflowEventMock).toHaveBeenCalledWith(
expect.objectContaining({
eventType: 'FILE_UPLOADED',
payload: expect.objectContaining({
fileId: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
fileName: 'sample.txt',
contentType: 'text/plain',
sizeBytes: 3,
storageKey: 'tenant-1/files/sample.txt',
}),
ctx: expect.objectContaining({ tenantId: 'tenant-1' }),
})
);
});
it('publishes DOCUMENT_DELETED after deleting the file record', async () => {
getStorageConfigMock.mockResolvedValue({ defaultProvider: 'local' } as any);
getProviderConfigMock.mockResolvedValue({ type: 'local' } as any);
createProviderMock.mockResolvedValue({
delete: vi.fn(async () => {}),
} as any);
createTenantKnexMock.mockResolvedValue({ knex: {}, tenant: 'tenant-1' } as any);
fileFindByIdMock
.mockResolvedValueOnce({
file_id: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
original_name: 'sample.txt',
mime_type: 'text/plain',
file_size: 3,
storage_path: 'tenant-1/files/sample.txt',
uploaded_by_id: 'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0',
created_at: '2026-01-24T12:00:00.000Z',
} as any)
.mockResolvedValueOnce({
file_id: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
deleted_at: '2026-01-24T12:30:00.000Z',
} as any);
fileSoftDeleteMock.mockResolvedValue(undefined as any);
await StorageService.deleteFile(
'14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0'
);
expect(publishWorkflowEventMock).toHaveBeenCalledWith(
expect.objectContaining({
eventType: 'DOCUMENT_DELETED',
payload: expect.objectContaining({
documentId: '14f1fbf4-17d6-4bdc-8d4b-0b2a2ff8f26a',
deletedByUserId: 'a836a8b5-3df5-47b1-b49b-9a78f2b1a8a0',
deletedAt: '2026-01-24T12:30:00.000Z',
}),
ctx: expect.objectContaining({ tenantId: 'tenant-1' }),
})
);
});
});