import { describe, it, expect } from 'vitest';
import { mutateSequence } from '../../src/tools/mutateSequence';
import { testSequences } from '../fixtures/sequences';
describe('mutateSequence Tool', () => {
describe('Basic functionality', () => {
it('should mutate DNA sequences', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0.1,
seed: 12345
});
expect(result.content).toHaveLength(1);
expect(result.content[0].type).toBe('text');
const data = JSON.parse(result.content[0].text);
expect(data.statistics.sequenceType).toBe('dna');
expect(data.statistics.originalLength).toBe(testSequences.dna.medium.length);
expect(Array.isArray(data.mutations)).toBe(true);
});
it('should mutate protein sequences', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.protein.medium,
sequenceType: 'protein',
substitutionRate: 0.1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.sequenceType).toBe('protein');
expect(data.statistics.originalLength).toBe(testSequences.protein.medium.length);
});
it('should use seed for reproducible results', async () => {
const seed = 98765;
const params = {
sequence: testSequences.dna.short,
sequenceType: 'dna' as const,
substitutionRate: 0.2,
seed
};
const result1 = await mutateSequence.handler(params);
const result2 = await mutateSequence.handler(params);
expect(result1.content[0].text).toBe(result2.content[0].text);
});
});
describe('Mutation types', () => {
it('should perform substitution mutations', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0.5,
insertionRate: 0,
deletionRate: 0,
iterations: 1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
const changes = data.mutations[1]?.changes || [];
if (changes.length > 0) {
const substitutions = changes.filter((change: any) => change.type === 'substitution');
expect(substitutions.length).toBeGreaterThan(0);
}
});
it('should perform insertion mutations', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.short,
sequenceType: 'dna',
substitutionRate: 0,
insertionRate: 0.5,
deletionRate: 0,
iterations: 1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.finalLength).toBeGreaterThanOrEqual(data.statistics.originalLength);
});
it('should perform deletion mutations', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0,
insertionRate: 0,
deletionRate: 0.3,
iterations: 1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.finalLength).toBeLessThanOrEqual(data.statistics.originalLength);
});
});
describe('Multiple iterations', () => {
it('should track changes across iterations', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.short,
sequenceType: 'dna',
substitutionRate: 0.2,
iterations: 5,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.mutations).toHaveLength(6); // Original + 5 iterations
expect(data.mutations[0].iteration).toBe(0);
expect(data.mutations[5].iteration).toBe(5);
// First iteration should be the original sequence
expect(data.mutations[0].sequence).toBe(testSequences.dna.short.toUpperCase());
expect(data.mutations[0].changes).toEqual([]);
});
it('should accumulate changes over iterations', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0.1,
iterations: 3,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.totalIterations).toBe(3);
expect(data.statistics.totalChanges).toBeGreaterThanOrEqual(0);
});
});
describe('DNA-specific mutations', () => {
it('should respect transition bias', async () => {
// Test with strong transition bias
const result = await mutateSequence.handler({
sequence: 'A'.repeat(100),
sequenceType: 'dna',
substitutionRate: 0.5,
transitionBias: 10.0,
iterations: 1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
const finalSequence = data.mutations[1]?.sequence || '';
// Count transitions (A->G) vs transversions (A->T, A->C)
let transitions = 0;
let transversions = 0;
for (let i = 0; i < finalSequence.length; i++) {
const base = finalSequence[i];
if (base === 'G') transitions++;
if (base === 'T' || base === 'C') transversions++;
}
if (transitions + transversions > 0) {
const ratio = transitions / (transversions || 1);
expect(ratio).toBeGreaterThan(1); // Should favor transitions
}
});
it('should maintain valid DNA bases', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0.8,
insertionRate: 0.1,
deletionRate: 0.1,
iterations: 3,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
data.mutations.forEach((mutation: any) => {
expect(mutation.sequence).toMatch(/^[ATGC]+$/);
});
});
});
describe('Protein-specific mutations', () => {
it('should maintain valid amino acids', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.protein.medium,
sequenceType: 'protein',
substitutionRate: 0.5,
insertionRate: 0.1,
deletionRate: 0.1,
iterations: 3,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
const validAA = /^[ARNDCQEGHILKMFPSTWYV]+$/;
data.mutations.forEach((mutation: any) => {
expect(mutation.sequence).toMatch(validAA);
});
});
});
describe('Change tracking', () => {
it('should identify substitution changes correctly', async () => {
const result = await mutateSequence.handler({
sequence: 'ATGC',
sequenceType: 'dna',
substitutionRate: 1.0, // Force substitutions
insertionRate: 0,
deletionRate: 0,
iterations: 1,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
const changes = data.mutations[1]?.changes || [];
changes.forEach((change: any) => {
if (change.type === 'substitution') {
expect(change.original).toBeDefined();
expect(change.mutated).toBeDefined();
expect(change.position).toBeGreaterThanOrEqual(0);
expect(change.original).not.toBe(change.mutated);
}
});
});
it('should track change counts correctly', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.medium,
sequenceType: 'dna',
substitutionRate: 0.1,
iterations: 2,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
let totalFromIterations = 0;
for (let i = 1; i < data.mutations.length; i++) {
totalFromIterations += data.mutations[i].changeCount || 0;
}
expect(data.statistics.totalChanges).toBe(totalFromIterations);
});
});
describe('Output formats', () => {
it('should output FASTA format by default', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.short,
sequenceType: 'dna',
substitutionRate: 0.1,
outputFormat: 'fasta'
});
const data = JSON.parse(result.content[0].text);
expect(typeof data.sequences).toBe('string');
expect(data.sequences).toMatch(/^>mutated_dna_iter_0/);
});
it('should output plain format when requested', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.short,
sequenceType: 'dna',
substitutionRate: 0.1,
outputFormat: 'plain'
});
const data = JSON.parse(result.content[0].text);
expect(typeof data.rawOutput).toBe('string');
expect(data.rawOutput).toMatch(/^[ATGC]+/);
});
});
describe('Input validation', () => {
it('should handle empty sequences gracefully', async () => {
const result = await mutateSequence.handler({
sequence: 'A',
sequenceType: 'dna',
substitutionRate: 0.1
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.originalLength).toBe(1);
});
it('should handle extreme mutation rates', async () => {
const result = await mutateSequence.handler({
sequence: testSequences.dna.short,
sequenceType: 'dna',
substitutionRate: 1.0,
insertionRate: 0.5,
deletionRate: 0.5,
seed: 12345
});
const data = JSON.parse(result.content[0].text);
expect(data.statistics.finalLength).toBeGreaterThan(0);
});
});
describe('Tool definition', () => {
it('should have correct tool definition structure', () => {
expect(mutateSequence.definition.name).toBe('mutate_sequence');
expect(mutateSequence.definition.description).toContain('Apply mutations');
expect(mutateSequence.definition.inputSchema.type).toBe('object');
expect(mutateSequence.definition.inputSchema.required).toEqual(['sequence', 'sequenceType']);
});
it('should have proper parameter definitions', () => {
const props = mutateSequence.definition.inputSchema.properties;
expect(props.sequence.type).toBe('string');
expect(props.sequenceType.enum).toEqual(['dna', 'protein']);
expect(props.substitutionRate.minimum).toBe(0);
expect(props.substitutionRate.maximum).toBe(1);
expect(props.iterations.minimum).toBe(1);
});
});
});