fix(blob): rewrite tests for storage-delegated API
- Old tests imported removed functions (getBlobServiceClient, getContainerClient) - Old tests mocked @azure/storage-blob directly but blob now delegates to @bytelyst/storage - New tests use MemoryStorageProvider for functional validation - Tests cover: isBlobStorageConfigured (4 cases), storage provider/bucket ops (5 cases), BLOB_CONTAINERS (1 case) - 10/10 tests passing (was 2/8)
This commit is contained in:
parent
4ca9b73d75
commit
9438085cc0
@ -1,146 +1,108 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const {
|
||||
MockBlobServiceClient,
|
||||
mockFromConnectionString,
|
||||
mockGetContainerClient,
|
||||
MockStorageSharedKeyCredential,
|
||||
mockGenerateBlobSASQueryParameters,
|
||||
} = vi.hoisted(() => {
|
||||
const mockGetContainerClient = vi.fn(() => ({
|
||||
createIfNotExists: vi.fn(),
|
||||
}));
|
||||
|
||||
const mockFromConnectionString = vi.fn(() => ({
|
||||
getContainerClient: mockGetContainerClient,
|
||||
}));
|
||||
|
||||
const MockBlobServiceClient = vi.fn(() => ({
|
||||
getContainerClient: mockGetContainerClient,
|
||||
})) as unknown as {
|
||||
new (...args: any[]): any;
|
||||
fromConnectionString: typeof mockFromConnectionString;
|
||||
};
|
||||
|
||||
(MockBlobServiceClient as any).fromConnectionString = mockFromConnectionString;
|
||||
|
||||
const MockStorageSharedKeyCredential = vi.fn(() => ({ kind: 'shared-key-cred' }));
|
||||
|
||||
const mockGenerateBlobSASQueryParameters = vi.fn(() => ({
|
||||
toString: () => 'sig=fake',
|
||||
}));
|
||||
|
||||
return {
|
||||
MockBlobServiceClient,
|
||||
mockFromConnectionString,
|
||||
mockGetContainerClient,
|
||||
MockStorageSharedKeyCredential,
|
||||
mockGenerateBlobSASQueryParameters,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock('@azure/storage-blob', () => ({
|
||||
BlobServiceClient: MockBlobServiceClient,
|
||||
ContainerClient: class {},
|
||||
StorageSharedKeyCredential: MockStorageSharedKeyCredential,
|
||||
BlobSASPermissions: class {
|
||||
read = false;
|
||||
write = false;
|
||||
create = false;
|
||||
delete = false;
|
||||
},
|
||||
generateBlobSASQueryParameters: mockGenerateBlobSASQueryParameters,
|
||||
SASProtocol: { Https: 'Https' },
|
||||
}));
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { setStorage, MemoryStorageProvider } from '@bytelyst/storage';
|
||||
|
||||
import {
|
||||
_resetBlobClient,
|
||||
generateSasUrl,
|
||||
getBlobServiceClient,
|
||||
getContainerClient,
|
||||
getBucket,
|
||||
getStorageProvider,
|
||||
isBlobStorageConfigured,
|
||||
BLOB_CONTAINERS,
|
||||
} from '../index.js';
|
||||
|
||||
describe('blob', () => {
|
||||
beforeEach(() => {
|
||||
_resetBlobClient();
|
||||
MockBlobServiceClient.mockClear();
|
||||
mockFromConnectionString.mockClear();
|
||||
mockGetContainerClient.mockClear();
|
||||
MockStorageSharedKeyCredential.mockClear();
|
||||
mockGenerateBlobSASQueryParameters.mockClear();
|
||||
|
||||
delete process.env.AZURE_BLOB_CONNECTION_STRING;
|
||||
delete process.env.AZURE_BLOB_ACCOUNT_NAME;
|
||||
delete process.env.AZURE_BLOB_ACCOUNT_KEY;
|
||||
delete process.env.STORAGE_PROVIDER;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
_resetBlobClient();
|
||||
delete process.env.AZURE_BLOB_CONNECTION_STRING;
|
||||
delete process.env.AZURE_BLOB_ACCOUNT_NAME;
|
||||
delete process.env.AZURE_BLOB_ACCOUNT_KEY;
|
||||
delete process.env.STORAGE_PROVIDER;
|
||||
});
|
||||
|
||||
it('isBlobStorageConfigured is false when unset', () => {
|
||||
expect(isBlobStorageConfigured()).toBe(false);
|
||||
describe('isBlobStorageConfigured', () => {
|
||||
it('is false when unset', () => {
|
||||
expect(isBlobStorageConfigured()).toBe(false);
|
||||
});
|
||||
|
||||
it('is true when connection string is set', () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=x;AccountKey=y;';
|
||||
expect(isBlobStorageConfigured()).toBe(true);
|
||||
});
|
||||
|
||||
it('is true when account name + key are set', () => {
|
||||
process.env.AZURE_BLOB_ACCOUNT_NAME = 'acc';
|
||||
process.env.AZURE_BLOB_ACCOUNT_KEY = 'key==';
|
||||
expect(isBlobStorageConfigured()).toBe(true);
|
||||
});
|
||||
|
||||
it('is true when provider is memory', () => {
|
||||
process.env.STORAGE_PROVIDER = 'memory';
|
||||
expect(isBlobStorageConfigured()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('isBlobStorageConfigured is true when connection string is set', () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=x;AccountKey=y;';
|
||||
expect(isBlobStorageConfigured()).toBe(true);
|
||||
describe('with memory provider', () => {
|
||||
let memoryProvider: MemoryStorageProvider;
|
||||
|
||||
beforeEach(() => {
|
||||
memoryProvider = new MemoryStorageProvider();
|
||||
setStorage(memoryProvider);
|
||||
});
|
||||
|
||||
it('getStorageProvider returns the provider', async () => {
|
||||
const provider = await getStorageProvider();
|
||||
expect(provider).toBe(memoryProvider);
|
||||
});
|
||||
|
||||
it('getBucket returns a bucket by name', async () => {
|
||||
const bucket = await getBucket('audio');
|
||||
expect(bucket).toBeDefined();
|
||||
// Upload and download to verify it works
|
||||
await bucket.upload('test.wav', Buffer.from('hello'));
|
||||
const data = await bucket.download('test.wav');
|
||||
expect(data.toString()).toBe('hello');
|
||||
});
|
||||
|
||||
it('generateSasUrl returns a signed URL', async () => {
|
||||
const url = await generateSasUrl('audio', 'path/file.wav', 'r', 10);
|
||||
expect(url).toContain('audio');
|
||||
expect(url).toContain('path/file.wav');
|
||||
expect(url).toContain('signed=true');
|
||||
});
|
||||
|
||||
it('generateSasUrl defaults to read permissions', async () => {
|
||||
const url = await generateSasUrl('audio', 'file.wav');
|
||||
expect(url).toContain('signed=true');
|
||||
});
|
||||
|
||||
it('_resetBlobClient resets the storage singleton', async () => {
|
||||
const p1 = await getStorageProvider();
|
||||
_resetBlobClient();
|
||||
// After reset, inject a new provider
|
||||
const newProvider = new MemoryStorageProvider();
|
||||
setStorage(newProvider);
|
||||
const p2 = await getStorageProvider();
|
||||
expect(p1).not.toBe(p2);
|
||||
});
|
||||
});
|
||||
|
||||
it('getBlobServiceClient uses connection string when present', () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=acc;AccountKey=key==;';
|
||||
const c1 = getBlobServiceClient();
|
||||
const c2 = getBlobServiceClient();
|
||||
expect(c1).toBe(c2);
|
||||
expect(mockFromConnectionString).toHaveBeenCalledTimes(1);
|
||||
expect(MockBlobServiceClient).not.toHaveBeenCalled(); // should not call constructor in this path
|
||||
});
|
||||
|
||||
it('getBlobServiceClient uses account name + key when provided', () => {
|
||||
process.env.AZURE_BLOB_ACCOUNT_NAME = 'acc';
|
||||
process.env.AZURE_BLOB_ACCOUNT_KEY = 'key==';
|
||||
|
||||
getBlobServiceClient();
|
||||
|
||||
expect(MockStorageSharedKeyCredential).toHaveBeenCalledWith('acc', 'key==');
|
||||
expect(MockBlobServiceClient).toHaveBeenCalledWith(
|
||||
'https://acc.blob.core.windows.net',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
|
||||
it('getBlobServiceClient throws when credentials missing', () => {
|
||||
expect(() => getBlobServiceClient()).toThrow('Azure Blob Storage not configured');
|
||||
});
|
||||
|
||||
it('getContainerClient creates container and caches client', async () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=acc;AccountKey=key==;';
|
||||
|
||||
const c1 = await getContainerClient('audio');
|
||||
const c2 = await getContainerClient('audio');
|
||||
expect(c1).toBe(c2);
|
||||
|
||||
expect(mockGetContainerClient).toHaveBeenCalledTimes(1);
|
||||
expect(c1.createIfNotExists).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('generateSasUrl uses parsed connection string when account vars not set', () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=acc;AccountKey=key==;';
|
||||
|
||||
const url = generateSasUrl('audio', 'path/file.wav', 'rw', 10);
|
||||
expect(url).toContain('https://acc.blob.core.windows.net/audio/path/file.wav?');
|
||||
expect(mockGenerateBlobSASQueryParameters).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('generateSasUrl prefers explicit account vars when provided', () => {
|
||||
process.env.AZURE_BLOB_CONNECTION_STRING = 'AccountName=fromconn;AccountKey=key==;';
|
||||
process.env.AZURE_BLOB_ACCOUNT_NAME = 'acc';
|
||||
process.env.AZURE_BLOB_ACCOUNT_KEY = 'key==';
|
||||
|
||||
const url = generateSasUrl('audio', 'x', 'r', 1);
|
||||
expect(url).toContain('https://acc.blob.core.windows.net/audio/x?');
|
||||
describe('BLOB_CONTAINERS', () => {
|
||||
it('has expected container names', () => {
|
||||
expect(BLOB_CONTAINERS.audio).toBe('audio');
|
||||
expect(BLOB_CONTAINERS.transcripts).toBe('transcripts');
|
||||
expect(BLOB_CONTAINERS.attachments).toBe('attachments');
|
||||
expect(BLOB_CONTAINERS.avatars).toBe('avatars');
|
||||
expect(BLOB_CONTAINERS.releases).toBe('releases');
|
||||
expect(BLOB_CONTAINERS.backups).toBe('backups');
|
||||
expect(BLOB_CONTAINERS.feedbackScreenshots).toBe('feedback-screenshots');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user