|
7 | 7 | """ |
8 | 8 |
|
9 | 9 | import datetime |
| 10 | +import gzip |
10 | 11 | import logging |
11 | 12 | import os |
12 | 13 | import subprocess |
@@ -197,6 +198,86 @@ def test_bcftools_pipe_query( |
197 | 198 | ) |
198 | 199 |
|
199 | 200 |
|
| 201 | +@pytest.mark.parametrize( |
| 202 | + "arg_command,expected_records,expected_view_command_filter_fragment", |
| 203 | + [ |
| 204 | + ( |
| 205 | + r"view -i 'FILTER~\"q10\"'", |
| 206 | + [("17330", ".", "q10"), ("1234567", "microsat1", "q10;s50")], |
| 207 | + 'FILTER~"q10"', |
| 208 | + ), |
| 209 | + ( |
| 210 | + r"view -i 'FILTER=\"q10\"'", |
| 211 | + [("17330", ".", "q10")], |
| 212 | + 'FILTER="q10"', |
| 213 | + ), |
| 214 | + ( |
| 215 | + r"view -i 'FILTER=\"q10;s50\"'", |
| 216 | + [("1234567", "microsat1", "q10;s50")], |
| 217 | + 'FILTER="q10;s50"', |
| 218 | + ), |
| 219 | + ], |
| 220 | + ids=[ |
| 221 | + "filter-subset-match-q10", |
| 222 | + "filter-exact-match-q10", |
| 223 | + "filter-exact-match-q10-s50", |
| 224 | + ], |
| 225 | +) |
| 226 | +def test_bcftools_pipe_query_supports_semicolon_in_filter_expression_e2e( |
| 227 | + CONSTANTS, |
| 228 | + logged_in_edit_user_with_existing_config, |
| 229 | + run_update_dimensions, |
| 230 | + project_map, |
| 231 | + fixtures_dir, |
| 232 | + cleaned_project_bucket, |
| 233 | + arg_command, |
| 234 | + expected_records, |
| 235 | + expected_view_command_filter_fragment, |
| 236 | +): |
| 237 | + """ |
| 238 | + Test that VCF queries can support bcftools view -i FILTER expressions (including semicolon values) |
| 239 | + """ |
| 240 | + project_name, bucket_name = cleaned_project_bucket |
| 241 | + project_id = project_map[project_name] |
| 242 | + user_id = 1 |
| 243 | + fixture_name = "vcf_specification_v45_example11.vcf.gz" |
| 244 | + fixture_path = (fixtures_dir / fixture_name).resolve() |
| 245 | + upload_result = runner.invoke(app, f"files upload {fixture_path} --project {project_name}") |
| 246 | + assert upload_result.exit_code == 0, f"Upload failed: {upload_result.stdout}" |
| 247 | + |
| 248 | + run_update_dimensions(bucket_name=bucket_name, project_id=project_id, project_name=project_name, user_id=user_id) |
| 249 | + |
| 250 | + query_result = runner.invoke( |
| 251 | + app, |
| 252 | + f'query vcf --samples "NA00001,NA00002,NA00003" --command "{arg_command}" --project {project_name}', |
| 253 | + ) |
| 254 | + assert query_result.exit_code == 0, f"Query submission failed: {query_result.stdout}" |
| 255 | + assert "Job submitted" in query_result.stdout |
| 256 | + |
| 257 | + user_task_id = query_result.stdout.strip().split()[-1] |
| 258 | + task_result = wait_for_task_complete(user_task_id=user_task_id) |
| 259 | + assert task_result.status == "SUCCESS", f"Task failed: {task_result.result}" |
| 260 | + assert hasattr(task_result.result, "output_file"), f"Missing output_file in task result: {task_result.result}" |
| 261 | + output_file = task_result.result.output_file |
| 262 | + |
| 263 | + stream_result = runner.invoke( |
| 264 | + app, |
| 265 | + f"files stream {output_file} --project {project_name}", |
| 266 | + ) |
| 267 | + assert stream_result.exit_code == 0, f"Streaming query result failed: {stream_result.stdout}" |
| 268 | + |
| 269 | + streamed_vcf_content = gzip.decompress(stream_result.stdout_bytes).decode("utf-8") |
| 270 | + assert "##bcftools_viewCommand=" in streamed_vcf_content |
| 271 | + assert expected_view_command_filter_fragment in streamed_vcf_content |
| 272 | + |
| 273 | + records = [line for line in streamed_vcf_content.splitlines() if line and not line.startswith("#")] |
| 274 | + parsed_records = [(cols[1], cols[2], cols[6]) for cols in (record.split("\t") for record in records)] |
| 275 | + |
| 276 | + assert parsed_records == expected_records, ( |
| 277 | + f"Unexpected records for command '{arg_command}'. Expected {expected_records}, got {parsed_records}" |
| 278 | + ) |
| 279 | + |
| 280 | + |
200 | 281 | def test_bcftools_pipe_query_direct_samples_mode( |
201 | 282 | CONSTANTS, |
202 | 283 | logged_in_edit_user_with_existing_config, |
|
0 commit comments