|
1 | | -import io |
2 | 1 | import json |
3 | | -import csv |
4 | 2 | from datetime import timedelta |
5 | 3 |
|
6 | 4 | from django.test import TestCase, RequestFactory, mock |
7 | 5 | from django.contrib.auth import get_user_model |
| 6 | +from django.core import exceptions |
| 7 | +from django.http import HttpResponse |
8 | 8 |
|
9 | 9 | from rest_framework import exceptions |
10 | 10 |
|
|
17 | 17 | ScoreSetFactory, ExperimentFactory, ExperimentSetFactory |
18 | 18 | ) |
19 | 19 |
|
20 | | -from variant.factories import dna_hgvs, protein_hgvs |
| 20 | +from variant.factories import VariantFactory |
21 | 21 | from variant.models import Variant |
22 | 22 |
|
23 | 23 | from .. import views |
@@ -268,6 +268,148 @@ def test_list_includes_private_when_authenticated(self): |
268 | 268 | self.assertContains(response, instance2.urn) |
269 | 269 |
|
270 | 270 |
|
| 271 | +class TestFormatCSVRows(TestCase): |
| 272 | + def test_dicts_include_urn(self): |
| 273 | + vs = [VariantFactory() for _ in range(5)] |
| 274 | + rows = views.format_csv_rows( |
| 275 | + vs, columns=['score', 'urn', ], |
| 276 | + dtype=constants.variant_score_data |
| 277 | + ) |
| 278 | + for v, row in zip(vs, rows): |
| 279 | + self.assertEqual(v.urn, row['urn']) |
| 280 | + |
| 281 | + def test_dicts_include_nt_hgvs(self): |
| 282 | + vs = [VariantFactory() for _ in range(5)] |
| 283 | + rows = views.format_csv_rows( |
| 284 | + vs, columns=['score', constants.hgvs_nt_column, ], |
| 285 | + dtype=constants.variant_score_data |
| 286 | + ) |
| 287 | + for v, row in zip(vs, rows): |
| 288 | + self.assertEqual(v.hgvs_nt, row[constants.hgvs_nt_column]) |
| 289 | + |
| 290 | + def test_dicts_include_pro_hgvs(self): |
| 291 | + vs = [VariantFactory() for _ in range(5)] |
| 292 | + rows = views.format_csv_rows( |
| 293 | + vs, columns=['score', constants.hgvs_pro_column, ], |
| 294 | + dtype=constants.variant_score_data |
| 295 | + ) |
| 296 | + for v, row in zip(vs, rows): |
| 297 | + self.assertEqual(v.hgvs_pro, row[constants.hgvs_pro_column]) |
| 298 | + |
| 299 | + def test_dicts_include_data_columns_as_strings(self): |
| 300 | + vs = [VariantFactory(data={ |
| 301 | + constants.variant_score_data: {'score': 1, 'se': 2.12}}) |
| 302 | + for _ in range(5) |
| 303 | + ] |
| 304 | + rows = views.format_csv_rows( |
| 305 | + vs, columns=['score', 'se', ], |
| 306 | + dtype=constants.variant_score_data |
| 307 | + ) |
| 308 | + for v, row in zip(vs, rows): |
| 309 | + self.assertEqual('1', row['score']) |
| 310 | + self.assertEqual('2.12', row['se']) |
| 311 | + |
| 312 | + |
| 313 | +class TestValidateRequest(TestCase): |
| 314 | + def setUp(self): |
| 315 | + self.factory = RequestFactory() |
| 316 | + self.user = UserFactory() |
| 317 | + self.request = self.factory.get('/') |
| 318 | + self.request.user = self.user |
| 319 | + self.instance = ScoreSetFactory(private=True) |
| 320 | + |
| 321 | + def test_returns_404_response_when_urn_model_not_found(self): |
| 322 | + response = views.validate_request(self.request, 'urn') |
| 323 | + self.assertEqual(response.status_code, 404) |
| 324 | + |
| 325 | + @mock.patch('api.views.validate_request') |
| 326 | + def test_calls_authenticate(self, patch): |
| 327 | + views.validate_request(self.request, self.instance.urn) |
| 328 | + patch.assert_called() |
| 329 | + |
| 330 | + @mock.patch('api.views.validate_request') |
| 331 | + def test_calls_check_permission(self, patch): |
| 332 | + views.validate_request(self.request, self.instance.urn) |
| 333 | + patch.assert_called() |
| 334 | + |
| 335 | + |
| 336 | +class TestFormatResponse(TestCase): |
| 337 | + def setUp(self): |
| 338 | + self.user = UserFactory() |
| 339 | + self.instance = ScoreSetFactory(private=True) |
| 340 | + self.response = HttpResponse(content_type='text/csv') |
| 341 | + |
| 342 | + def test_adds_comments_to_response(self): |
| 343 | + response = views.format_response( |
| 344 | + self.response, self.instance, dtype='scores') |
| 345 | + content = response.content.decode() |
| 346 | + self.assertIn("# URN: {}".format(self.instance.urn), content) |
| 347 | + self.assertIn("# Downloaded (UTC):", content) |
| 348 | + self.assertIn("# Licence: {}".format( |
| 349 | + self.instance.licence.long_name), content) |
| 350 | + self.assertIn("# Licence URL: {}".format( |
| 351 | + self.instance.licence.link), content) |
| 352 | + |
| 353 | + def test_raises_valueerror_unknown_dtype(self): |
| 354 | + with self.assertRaises(ValueError): |
| 355 | + views.format_response(self.response, self.instance, dtype='---') |
| 356 | + |
| 357 | + @mock.patch("api.views.format_csv_rows") |
| 358 | + def test_calls_format_csv_correct_call_dtype_is_scores(self, patch): |
| 359 | + self.instance.dataset_columns = { |
| 360 | + constants.score_columns: ['score', 'se']} |
| 361 | + self.instance.save() |
| 362 | + for i in range(5): |
| 363 | + data = {constants.variant_score_data: {'score': i, 'se': 2*i}} |
| 364 | + VariantFactory(scoreset=self.instance, data=data) |
| 365 | + |
| 366 | + _ = views.format_response( |
| 367 | + self.response, self.instance, dtype='scores') |
| 368 | + |
| 369 | + called_dtype = patch.call_args[1]['dtype'] |
| 370 | + called_columns = patch.call_args[1]['columns'] |
| 371 | + expected_columns = ['urn'] + self.instance.score_columns |
| 372 | + self.assertEqual(called_dtype, constants.variant_score_data) |
| 373 | + self.assertListEqual(called_columns, expected_columns) |
| 374 | + |
| 375 | + @mock.patch("api.views.format_csv_rows") |
| 376 | + def test_calls_format_csv_correct_call_dtype_is_counts(self, patch): |
| 377 | + self.instance.dataset_columns = { |
| 378 | + constants.count_columns: ['count', 'se']} |
| 379 | + self.instance.save() |
| 380 | + for i in range(5): |
| 381 | + data = {constants.variant_count_data: {'count': i, 'se': 2*i}} |
| 382 | + VariantFactory(scoreset=self.instance, data=data) |
| 383 | + |
| 384 | + _ = views.format_response( |
| 385 | + self.response, self.instance, dtype='counts') |
| 386 | + |
| 387 | + called_dtype = patch.call_args[1]['dtype'] |
| 388 | + called_columns = patch.call_args[1]['columns'] |
| 389 | + expected_columns = ['urn'] + self.instance.count_columns |
| 390 | + self.assertEqual(called_dtype, constants.variant_count_data) |
| 391 | + self.assertListEqual(called_columns, expected_columns) |
| 392 | + |
| 393 | + @mock.patch("api.views.format_csv_rows") |
| 394 | + def test_returns_empty_csv_when_no_additional_columns_present(self, patch): |
| 395 | + _ = views.format_response( |
| 396 | + self.response, self.instance, dtype='scores') |
| 397 | + patch.assert_not_called() |
| 398 | + |
| 399 | + def test_double_quotes_column_values_containing_commas(self): |
| 400 | + self.instance.dataset_columns = { |
| 401 | + constants.score_columns: ['hello,world',]} |
| 402 | + |
| 403 | + for i in range(5): |
| 404 | + data = {constants.variant_score_data: {'hello,world': i}} |
| 405 | + VariantFactory(scoreset=self.instance, data=data) |
| 406 | + |
| 407 | + response = views.format_response( |
| 408 | + self.response, self.instance, dtype='scores') |
| 409 | + content = response.content.decode() |
| 410 | + self.assertIn('"hello,world"', content) |
| 411 | + |
| 412 | + |
271 | 413 | class TestScoreSetAPIViews(TestCase): |
272 | 414 | factory = ScoreSetFactory |
273 | 415 | url = 'scoresets' |
@@ -388,183 +530,24 @@ def test_OK_private_download_meta_when_authenticated(self): |
388 | 530 | def test_404_not_found(self): |
389 | 531 | response = self.client.get("/api/scoresets/dddd/") |
390 | 532 | self.assertEqual(response.status_code, 404) |
391 | | - |
392 | | - def test_can_download_scores(self): |
393 | | - scs = self.factory() |
394 | | - scs = publish_dataset(scs) |
395 | | - scs.refresh_from_db() |
396 | | - scs.dataset_columns = { |
397 | | - constants.score_columns: ["score"], |
398 | | - constants.count_columns: ["count"] |
399 | | - } |
400 | | - scs.save() |
401 | | - variant = Variant.objects.create( |
402 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=protein_hgvs[0], |
403 | | - scoreset=scs, data={ |
404 | | - constants.variant_score_data: {"score": "1"}, |
405 | | - constants.variant_count_data: {"count": "1"} |
406 | | - } |
407 | | - ) |
408 | | - |
409 | | - response = self.client.get("/api/scoresets/{}/scores/".format(scs.urn)) |
410 | | - rows = list( |
411 | | - csv.reader( |
412 | | - io.TextIOWrapper( |
413 | | - io.BytesIO(response.content), encoding='utf-8'))) |
414 | | - |
415 | | - header = [constants.hgvs_nt_column, constants.hgvs_pro_column, 'score'] |
416 | | - data = [variant.hgvs_nt, variant.hgvs_pro, '1'] |
417 | | - self.assertEqual(rows, [header, data]) |
418 | | - |
419 | | - def test_comma_in_value_enclosed_by_quotes(self): |
420 | | - scs = self.factory() |
421 | | - scs = publish_dataset(scs) |
422 | | - scs.refresh_from_db() |
423 | | - scs.dataset_columns = { |
424 | | - constants.score_columns: ["score"], |
425 | | - constants.count_columns: ["count,count"] |
426 | | - } |
427 | | - scs.save(save_parents=True) |
428 | | - variant = Variant.objects.create( |
429 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=protein_hgvs[0], |
430 | | - scoreset=scs, data={ |
431 | | - constants.variant_score_data: {"score": "1"}, |
432 | | - constants.variant_count_data: {"count,count": "4"} |
433 | | - } |
434 | | - ) |
435 | | - response = self.client.get("/api/scoresets/{}/counts/".format(scs.urn)) |
436 | | - rows = list( |
437 | | - csv.reader( |
438 | | - io.TextIOWrapper( |
439 | | - io.BytesIO(response.content), encoding='utf-8'))) |
440 | | - |
441 | | - header = [constants.hgvs_nt_column, constants.hgvs_pro_column, 'count,count'] |
442 | | - data = [variant.hgvs_nt, variant.hgvs_pro, '4'] |
443 | | - self.assertEqual(rows, [header, data]) |
444 | | - |
445 | | - def test_can_download_counts(self): |
446 | | - scs = self.factory() |
447 | | - scs = publish_dataset(scs) |
448 | | - scs.refresh_from_db() |
449 | | - scs.dataset_columns = { |
450 | | - constants.score_columns: ["score"], |
451 | | - constants.count_columns: ["count"] |
452 | | - } |
453 | | - scs.save(save_parents=True) |
454 | | - variant = Variant.objects.create( |
455 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=protein_hgvs[0], |
456 | | - scoreset=scs, data={ |
457 | | - constants.variant_score_data: {"score": "1"}, |
458 | | - constants.variant_count_data: {"count": "4"} |
459 | | - } |
460 | | - ) |
461 | | - response = self.client.get("/api/scoresets/{}/counts/".format(scs.urn)) |
462 | | - rows = list( |
463 | | - csv.reader( |
464 | | - io.TextIOWrapper( |
465 | | - io.BytesIO(response.content), encoding='utf-8'))) |
466 | | - |
467 | | - header = [constants.hgvs_nt_column, constants.hgvs_pro_column, 'count'] |
468 | | - data = [variant.hgvs_nt, variant.hgvs_pro, '4'] |
469 | | - self.assertEqual(rows, [header, data]) |
470 | | - |
471 | | - def test_none_hgvs_written_as_blank(self): |
472 | | - scs = self.factory() |
473 | | - scs = publish_dataset(scs) |
474 | | - scs.refresh_from_db() |
475 | | - scs.dataset_columns = { |
476 | | - constants.score_columns: ["score"], |
477 | | - constants.count_columns: ["count"] |
478 | | - } |
479 | | - scs.save(save_parents=True) |
480 | | - variant = Variant.objects.create( |
481 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=None, |
482 | | - scoreset=scs, |
483 | | - data={ |
484 | | - constants.variant_score_data: {"score": "1"}, |
485 | | - constants.variant_count_data: {"count": "4"} |
486 | | - } |
487 | | - ) |
488 | | - response = self.client.get("/api/scoresets/{}/scores/".format(scs.urn)) |
489 | | - rows = list( |
490 | | - csv.reader( |
491 | | - io.TextIOWrapper( |
492 | | - io.BytesIO(response.content), encoding='utf-8'))) |
493 | | - |
494 | | - header = [constants.hgvs_nt_column, constants.hgvs_pro_column, 'score'] |
495 | | - data = [variant.hgvs_nt, '', '1'] |
496 | | - self.assertEqual(rows, [header, data]) |
497 | | - |
498 | | - def test_no_variants_empty_file(self): |
499 | | - scs = self.factory() |
500 | | - scs = publish_dataset(scs) |
501 | | - scs.refresh_from_db() |
502 | | - scs.dataset_columns = { |
503 | | - constants.score_columns: ["score"], |
504 | | - constants.count_columns: ["count"] |
505 | | - } |
506 | | - scs.save(save_parents=True) |
507 | | - scs.children.delete() |
508 | | - |
509 | | - response = self.client.get("/api/scoresets/{}/scores/".format(scs.urn)) |
510 | | - rows = list( |
511 | | - csv.reader( |
512 | | - io.TextIOWrapper( |
513 | | - io.BytesIO(response.content), encoding='utf-8'))) |
514 | | - self.assertEqual(rows, []) |
515 | | - |
516 | | - response = self.client.get("/api/scoresets/{}/counts/".format(scs.urn)) |
517 | | - rows = list( |
518 | | - csv.reader( |
519 | | - io.TextIOWrapper( |
520 | | - io.BytesIO(response.content), encoding='utf-8'))) |
521 | | - self.assertEqual(rows, []) |
522 | | - |
523 | | - def test_empty_scores_returns_empty_file(self): |
524 | | - scs = self.factory() |
525 | | - scs = publish_dataset(scs) |
526 | | - scs.refresh_from_db() |
527 | | - scs.dataset_columns = { |
528 | | - constants.score_columns: [], |
529 | | - constants.count_columns: ['count'] |
530 | | - } |
531 | | - scs.save(save_parents=True) |
532 | | - _ = Variant.objects.create( |
533 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=protein_hgvs[0], |
534 | | - scoreset=scs, data={ |
535 | | - constants.variant_score_data: {}, |
536 | | - constants.variant_count_data: {"count": "4"} |
537 | | - } |
538 | | - ) |
539 | | - response = self.client.get("/api/scoresets/{}/scores/".format(scs.urn)) |
540 | | - rows = list( |
541 | | - csv.reader( |
542 | | - io.TextIOWrapper( |
543 | | - io.BytesIO(response.content), encoding='utf-8'))) |
544 | | - self.assertEqual(rows, []) |
545 | | - |
546 | | - def test_empty_counts_returns_empty_file(self): |
547 | | - scs = self.factory() |
548 | | - scs = publish_dataset(scs) |
549 | | - scs.refresh_from_db() |
550 | | - scs.dataset_columns = { |
551 | | - constants.score_columns: ["score"], |
552 | | - constants.count_columns: [] |
553 | | - } |
554 | | - scs.save(save_parents=True) |
555 | | - _ = Variant.objects.create( |
556 | | - hgvs_nt=dna_hgvs[0], hgvs_pro=protein_hgvs[0], |
557 | | - scoreset=scs, data={ |
558 | | - constants.variant_score_data: {"score": "1"}, |
559 | | - constants.variant_count_data: {} |
560 | | - } |
561 | | - ) |
562 | | - response = self.client.get("/api/scoresets/{}/counts/".format(scs.urn)) |
563 | | - rows = list( |
564 | | - csv.reader( |
565 | | - io.TextIOWrapper( |
566 | | - io.BytesIO(response.content), encoding='utf-8'))) |
567 | | - self.assertEqual(rows, []) |
| 533 | + |
| 534 | + @mock.patch("api.views.format_response") |
| 535 | + def test_calls_format_response_with_dtype_scores(self, patch): |
| 536 | + request = RequestFactory().get('/') |
| 537 | + request.user = UserFactory() |
| 538 | + instance = self.factory(private=False) |
| 539 | + instance.add_viewers(request.user) |
| 540 | + views.scoreset_score_data(request, instance.urn) |
| 541 | + self.assertEqual(patch.call_args[1]['dtype'], 'scores') |
| 542 | + |
| 543 | + @mock.patch("api.views.format_response") |
| 544 | + def test_calls_format_response_with_dtype_counts(self, patch): |
| 545 | + request = RequestFactory().get('/') |
| 546 | + request.user = UserFactory() |
| 547 | + instance = self.factory(private=False) |
| 548 | + instance.add_viewers(request.user) |
| 549 | + views.scoreset_count_data(request, instance.urn) |
| 550 | + self.assertEqual(patch.call_args[1]['dtype'], 'counts') |
568 | 551 |
|
569 | 552 | def test_can_download_metadata(self): |
570 | 553 | scs = self.factory(private=False) |
|
0 commit comments