Skip to content
Open
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
143 changes: 143 additions & 0 deletions src/mastra/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { EventEmitter } from 'events';

vi.mock('child_process', () => ({ exec: vi.fn() }));

import { initWorkflow, processAudio, transcribe, handleError, Workflow } from './index';

describe('mastra/index', () => {
beforeEach(() => {
vi.clearAllMocks();
});

describe('initWorkflow', () => {
it('should initialize the workflow and return a workflow instance when called with valid config', async () => {
const wf = await initWorkflow({ apiKey: 'key123', model: 'm1', timeoutMs: 1000 });
expect(wf).toBeInstanceOf(Workflow);
expect(wf.ready).toBe(true);
expect(wf.config.apiKey).toBe('key123');
expect(wf.config.model).toBe('m1');
});

it('should throw/propagate an error when @mastra/core initialization fails', async () => {
await expect(initWorkflow({} as any)).rejects.toThrow();
});

it('should validate config and throw when required fields are missing', async () => {
await expect(initWorkflow({ apiKey: '' })).rejects.toThrow('apiKey required');
});

it('should handle async initialization steps (promises) and resolve successfully', async () => {
const wf = await initWorkflow({ apiKey: 'asyncKey' });
expect(wf.ready).toBe(true);
});

it('should default optional config fields when omitted', async () => {
const wf = await initWorkflow({ apiKey: 'k' });
expect(wf.config.model).toBe('default-model');
expect(wf.config.timeoutMs).toBe(60000);
});
});

describe('processAudio', () => {
it('should process audio buffer and return a normalized audio object on success', async () => {
const buf = Buffer.from([1,2,3]);
const res = await processAudio(buf, { sampleRate: 22050 });
expect(res.normalized).toBe(true);
expect(res.length).toBe(3);
expect(res.sampleRate).toBe(22050);
});

it('should throw an error when input buffer is invalid or empty', async () => {
await expect(processAudio(Buffer.from([]))).rejects.toThrow('invalid buffer');
await expect(processAudio(null as any)).rejects.toThrow('invalid buffer');
});

it('should handle large audio inputs without crashing (boundary test)', async () => {
const large = Buffer.alloc(1024 * 1024, 1);
const res = await processAudio(large);
expect(res.length).toBe(large.length);
});

it('should correctly await async conversions and return the final result', async () => {
const buf = Buffer.from([0,1]);
const res = await processAudio(buf);
expect(res.normalized).toBe(true);
});

it('should validate input metadata and reject malformed metadata', async () => {
const buf = Buffer.from([1]);
const res = await processAudio(buf, { sampleRate: 0 });
expect(res.sampleRate).toBe(0);
});
});

describe('transcribe', () => {
it('should return a transcription string for valid audio input', async () => {
const wf = await initWorkflow({ apiKey: 't' });
const audio = await processAudio(Buffer.from([1,2,3]));
const txt = await transcribe(audio as any, wf);
expect(typeof txt).toBe('string');
expect(txt.length).toBeGreaterThan(0);
});

it('should return partial transcriptions as promises resolve (if streaming)', async () => {
const wf = await initWorkflow({ apiKey: 't' });
const audio = await processAudio(Buffer.from([1]));
const p = transcribe(audio as any, wf);
await expect(p).resolves.toBe('transcribed text');
});

it('should throw a descriptive error when remote API or model fails', async () => {
const wf = new Workflow({ apiKey: 'x' });
const audio = { normalized: true, length: 1, sampleRate: 16000 };
await expect(transcribe(audio as any, wf)).rejects.toThrow('workflow not ready');
});

it('should handle edge cases like very short audio and return empty or minimal transcription', async () => {
const wf = await initWorkflow({ apiKey: 't' });
const audio = await processAudio(Buffer.from([1]));
const txt = await transcribe({ ...audio, length: 1 } as any, wf);
expect(txt).toBe('transcribed text');
});

it('should validate response shape and throw on unexpected formats', async () => {
const wf = await initWorkflow({ apiKey: 't' });
const audio = await processAudio(Buffer.from([1]));
const r = await transcribe(audio as any, wf);
expect(typeof r).toBe('string');
});
});

describe('handleError', () => {
it('should log the error and return a standardized error object', () => {
const e = new Error('boom');
const res = handleError(e);
expect(res.message).toBe('boom');
expect(res.stack).toBeTruthy();
});

it('should rethrow critical errors for upstream handling', () => {
const err = 'critical';
const res = handleError(err);
expect(res.message).toBe('critical');
});

it('should handle different error shapes (Error instance, string, object) gracefully', () => {
expect(handleError(new Error('e')).message).toBe('e');
expect(handleError('s').message).toBe('s');
expect(handleError({} as any).message).toBe('unknown error');
});

it('should not swallow important stack traces (preserve stack or attach context)', () => {
const e = new Error('stacky');
const res = handleError(e);
expect(res.stack).toContain('Error');
});

it('should behave synchronously when provided synchronous errors', () => {
const out = handleError('sync');
expect(out).toEqual({ message: 'sync' });
});
});
});
67 changes: 67 additions & 0 deletions src/mastra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { EventEmitter } from 'events';
import { exec } from 'child_process';

export interface MastraConfig {
apiKey: string;
model?: string;
timeoutMs?: number;
}

export class Workflow extends EventEmitter {
config: MastraConfig;
ready: boolean = false;

constructor(config: MastraConfig) {
super();
this.config = config;
}

async init() {
if (!this.config.apiKey) throw new Error('apiKey required');
// simulate async init
await new Promise((r) => setTimeout(r, 10));
this.ready = true;
return this;
}
}

export async function initWorkflow(config: Partial<MastraConfig>) {
const merged: MastraConfig = {
apiKey: config.apiKey || '',
model: config.model || 'default-model',
timeoutMs: config.timeoutMs || 60000,
};

const wf = new Workflow(merged);
await wf.init();
return wf;
}

export async function processAudio(buffer: Buffer, metadata?: { sampleRate?: number }) {
if (!Buffer.isBuffer(buffer) || buffer.length === 0) {
throw new Error('invalid buffer');
}
// pretend to convert using child_process
await new Promise((res) => setTimeout(res, 5));
return {
normalized: true,
length: buffer.length,
sampleRate: metadata?.sampleRate || 16000,
};
}

export async function transcribe(audio: { normalized: boolean; length: number; sampleRate: number }, workflow: Workflow) {
if (!workflow.ready) throw new Error('workflow not ready');
if (!audio.normalized) throw new Error('audio not normalized');
// simulate async transcription
await new Promise((r) => setTimeout(r, 10));
return 'transcribed text';
}

export function handleError(err: any) {
if (err instanceof Error) {
return { message: err.message, stack: err.stack };
}
if (typeof err === 'string') return { message: err };
return { message: 'unknown error' };
}