|
| 1 | + |
| 2 | +import pytest |
| 3 | +from pathlib import Path |
| 4 | +from Bio import SeqIO |
| 5 | +from Bio.Seq import Seq |
| 6 | +from Bio.SeqRecord import SeqRecord |
| 7 | + |
| 8 | + |
| 9 | +# Import functions/classes from our module. |
| 10 | +# (Assuming the code is in a module named append_primers_module.py.) |
| 11 | +from micall.utils.append_primers import ( |
| 12 | + append_primers_to_record, |
| 13 | + parse_arguments, |
| 14 | + append_primers, |
| 15 | + UserError, |
| 16 | + DEFAULT_FORWARD_PRIMER, |
| 17 | + DEFAULT_REVERSE_PRIMER, |
| 18 | +) |
| 19 | + |
| 20 | + |
| 21 | +def test_append_primers_to_record(): |
| 22 | + original_seq = "ATGCGT" |
| 23 | + record = SeqRecord(Seq(original_seq), id="test", description="dummy") |
| 24 | + fwd_primer = "AAA" |
| 25 | + rev_primer = "TTT" |
| 26 | + new_record = append_primers_to_record(record, fwd_primer, rev_primer) |
| 27 | + expected_seq = fwd_primer + original_seq + rev_primer |
| 28 | + assert str(new_record.seq) == expected_seq |
| 29 | + assert new_record.id == record.id |
| 30 | + assert new_record.description == record.description |
| 31 | + |
| 32 | + |
| 33 | +def test_parse_arguments_defaults(): |
| 34 | + # simulate command-line arguments, |
| 35 | + # note that the first two arguments are input and output files. |
| 36 | + test_args = [ |
| 37 | + "input.fa", |
| 38 | + "output.fa", |
| 39 | + ] |
| 40 | + args = parse_arguments(test_args) |
| 41 | + # Check that default primers are set. |
| 42 | + assert args.input_fasta == Path("input.fa") |
| 43 | + assert args.output_fasta == Path("output.fa") |
| 44 | + assert args.forward_primer == DEFAULT_FORWARD_PRIMER |
| 45 | + assert args.reverse_primer == DEFAULT_REVERSE_PRIMER |
| 46 | + |
| 47 | + |
| 48 | +def test_parse_arguments_custom_primers(): |
| 49 | + test_args = [ |
| 50 | + "in.fa", |
| 51 | + "out.fa", |
| 52 | + "--forward-primer", "CUSTOMFWD", |
| 53 | + "--reverse-primer", "CUSTOMREV" |
| 54 | + ] |
| 55 | + args = parse_arguments(test_args) |
| 56 | + assert args.forward_primer == "CUSTOMFWD" |
| 57 | + assert args.reverse_primer == "CUSTOMREV" |
| 58 | + |
| 59 | + |
| 60 | +@pytest.fixture |
| 61 | +def temp_fasta_file(tmp_path: Path) -> Path: |
| 62 | + # Create a temporary FASTA file with two records. |
| 63 | + fasta_path = tmp_path / "input.fa" |
| 64 | + records = [ |
| 65 | + SeqRecord(Seq("ATGC"), id="rec1", description="first"), |
| 66 | + SeqRecord(Seq("GATTACA"), id="rec2", description="second"), |
| 67 | + ] |
| 68 | + with fasta_path.open("w") as f: |
| 69 | + SeqIO.write(records, f, "fasta") |
| 70 | + return fasta_path |
| 71 | + |
| 72 | + |
| 73 | +def test_append_primers_default(temp_fasta_file: Path, tmp_path: Path): |
| 74 | + # Use default primers. |
| 75 | + output_path = tmp_path / "output.fa" |
| 76 | + append_primers( |
| 77 | + input=temp_fasta_file, |
| 78 | + output=output_path, |
| 79 | + forward_primer=DEFAULT_FORWARD_PRIMER, |
| 80 | + reverse_primer=DEFAULT_REVERSE_PRIMER, |
| 81 | + ) |
| 82 | + # Now read back the output. |
| 83 | + records = list(SeqIO.parse(str(output_path), "fasta")) |
| 84 | + # Check that the primers were appended. |
| 85 | + for rec in records: |
| 86 | + # The record sequence should start with DEFAULT_FORWARD_PRIMER |
| 87 | + # and end with DEFAULT_REVERSE_PRIMER. |
| 88 | + seq_str = str(rec.seq) |
| 89 | + assert seq_str.startswith(DEFAULT_FORWARD_PRIMER) |
| 90 | + assert seq_str.endswith(DEFAULT_REVERSE_PRIMER) |
| 91 | + # The middle part should be the original sequence. For instance, |
| 92 | + # for rec1, the original sequence was "ATGC". |
| 93 | + # We can extract the original: sequence[len(forward): -len(reverse)] |
| 94 | + original_seq = seq_str[len(DEFAULT_FORWARD_PRIMER): -len(DEFAULT_REVERSE_PRIMER)] |
| 95 | + # Check that the original sequence exists in the input file. |
| 96 | + # Without building a dict from the input file, we can simply check that |
| 97 | + # the extracted sequence is one of the known ones. |
| 98 | + assert original_seq in {"ATGC", "GATTACA"} |
| 99 | + |
| 100 | + |
| 101 | +def test_append_primers_custom(temp_fasta_file: Path, tmp_path: Path): |
| 102 | + output_path = tmp_path / "output_custom.fa" |
| 103 | + custom_fwd = "CUSTOMFWD" |
| 104 | + custom_rev = "CUSTOMREV" |
| 105 | + append_primers( |
| 106 | + input=temp_fasta_file, |
| 107 | + output=output_path, |
| 108 | + forward_primer=custom_fwd, |
| 109 | + reverse_primer=custom_rev, |
| 110 | + ) |
| 111 | + records = list(SeqIO.parse(str(output_path), "fasta")) |
| 112 | + for rec in records: |
| 113 | + seq_str = str(rec.seq) |
| 114 | + assert seq_str.startswith(custom_fwd) |
| 115 | + assert seq_str.endswith(custom_rev) |
| 116 | + |
| 117 | + |
| 118 | +def test_append_primers_missing_input(tmp_path: Path): |
| 119 | + # Create a non-existent input file path. |
| 120 | + missing_file = tmp_path / "non_existent.fa" |
| 121 | + output_file = tmp_path / "output.fa" |
| 122 | + with pytest.raises(UserError): |
| 123 | + append_primers( |
| 124 | + input=missing_file, |
| 125 | + output=output_file, |
| 126 | + forward_primer=DEFAULT_FORWARD_PRIMER, |
| 127 | + reverse_primer=DEFAULT_REVERSE_PRIMER, |
| 128 | + ) |
0 commit comments