|
4 | 4 |
|
5 | 5 | from appointment.database.models import CalendarProvider |
6 | 6 | from appointment.controller.calendar import CalDavConnector, GoogleConnector |
7 | | -from appointment.database import schemas, models |
| 7 | +from appointment.database import schemas, models, repo |
8 | 8 | from appointment.defines import GOOGLE_CALDAV_DOMAINS |
9 | 9 |
|
10 | 10 | from sqlalchemy import select |
@@ -511,3 +511,230 @@ def mock_bust_cached_events(self, all_calendars=False): |
511 | 511 | calendar_titles = [cal.title for cal in calendars] |
512 | 512 | assert 'Test Calendar 1' in calendar_titles |
513 | 513 | assert 'Test Calendar 2' in calendar_titles |
| 514 | + |
| 515 | + |
| 516 | +class TestCalendarUpdateOrCreate: |
| 517 | + """Tests for the update_or_create function in the calendar repository. |
| 518 | +
|
| 519 | + These tests ensure that multiple subscribers can connect the same external calendar |
| 520 | + (e.g., the same Google calendar) and each gets their own calendar record. |
| 521 | + """ |
| 522 | + |
| 523 | + def test_update_or_create_creates_new_calendar_when_none_exists( |
| 524 | + self, with_db, make_pro_subscriber, make_external_connections |
| 525 | + ): |
| 526 | + """Test that update_or_create creates a new calendar when no calendar exists with that URL.""" |
| 527 | + subscriber = make_pro_subscriber() |
| 528 | + ec = make_external_connections(subscriber.id, type=models.ExternalConnectionType.google) |
| 529 | + |
| 530 | + calendar_url = 'test-calendar@google.com' |
| 531 | + calendar_data = schemas.CalendarConnection( |
| 532 | + title='Test Calendar', |
| 533 | + color='#4285f4', |
| 534 | + provider=models.CalendarProvider.google, |
| 535 | + url=calendar_url, |
| 536 | + user=calendar_url, |
| 537 | + password='', |
| 538 | + ) |
| 539 | + |
| 540 | + with with_db() as db: |
| 541 | + result = repo.calendar.update_or_create( |
| 542 | + db=db, |
| 543 | + calendar=calendar_data, |
| 544 | + calendar_url=calendar_url, |
| 545 | + subscriber_id=subscriber.id, |
| 546 | + external_connection_id=ec.id, |
| 547 | + ) |
| 548 | + |
| 549 | + assert result is not None |
| 550 | + assert result.owner_id == subscriber.id |
| 551 | + assert result.url == calendar_url |
| 552 | + assert result.title == 'Test Calendar' |
| 553 | + assert result.external_connection_id == ec.id |
| 554 | + |
| 555 | + def test_update_or_create_updates_existing_calendar_for_same_subscriber( |
| 556 | + self, with_db, make_pro_subscriber, make_external_connections |
| 557 | + ): |
| 558 | + """Test that update_or_create updates an existing calendar when the same subscriber calls it.""" |
| 559 | + subscriber = make_pro_subscriber() |
| 560 | + ec = make_external_connections(subscriber.id, type=models.ExternalConnectionType.google) |
| 561 | + |
| 562 | + calendar_url = 'test-calendar@google.com' |
| 563 | + original_calendar = schemas.CalendarConnection( |
| 564 | + title='Original Title', |
| 565 | + color='#4285f4', |
| 566 | + provider=models.CalendarProvider.google, |
| 567 | + url=calendar_url, |
| 568 | + user=calendar_url, |
| 569 | + password='', |
| 570 | + ) |
| 571 | + |
| 572 | + with with_db() as db: |
| 573 | + # Create the initial calendar |
| 574 | + first_result = repo.calendar.update_or_create( |
| 575 | + db=db, |
| 576 | + calendar=original_calendar, |
| 577 | + calendar_url=calendar_url, |
| 578 | + subscriber_id=subscriber.id, |
| 579 | + external_connection_id=ec.id, |
| 580 | + ) |
| 581 | + first_calendar_id = first_result.id |
| 582 | + |
| 583 | + # Now call update_or_create again with updated data |
| 584 | + updated_calendar = schemas.CalendarConnection( |
| 585 | + title='Updated Title', |
| 586 | + color='#ff0000', |
| 587 | + provider=models.CalendarProvider.google, |
| 588 | + url=calendar_url, |
| 589 | + user=calendar_url, |
| 590 | + password='', |
| 591 | + ) |
| 592 | + |
| 593 | + second_result = repo.calendar.update_or_create( |
| 594 | + db=db, |
| 595 | + calendar=updated_calendar, |
| 596 | + calendar_url=calendar_url, |
| 597 | + subscriber_id=subscriber.id, |
| 598 | + external_connection_id=ec.id, |
| 599 | + ) |
| 600 | + |
| 601 | + # Should update the existing calendar, not create a new one |
| 602 | + assert second_result.id == first_calendar_id |
| 603 | + assert second_result.title == 'Updated Title' |
| 604 | + assert second_result.color == '#ff0000' |
| 605 | + |
| 606 | + # Verify only one calendar exists for this subscriber |
| 607 | + subscriber_calendars = repo.calendar.get_by_subscriber(db, subscriber.id) |
| 608 | + assert len(subscriber_calendars) == 1 |
| 609 | + |
| 610 | + def test_update_or_create_creates_separate_calendar_for_different_subscriber( |
| 611 | + self, with_db, make_pro_subscriber, make_external_connections |
| 612 | + ): |
| 613 | + """Test that update_or_create creates a new calendar when a different subscriber |
| 614 | + connects the same external calendar (e.g., same Google calendar URL) successfully. |
| 615 | + """ |
| 616 | + subscriber_a = make_pro_subscriber() |
| 617 | + subscriber_b = make_pro_subscriber() |
| 618 | + |
| 619 | + ec_a = make_external_connections(subscriber_a.id, type=models.ExternalConnectionType.google) |
| 620 | + ec_b = make_external_connections(subscriber_b.id, type=models.ExternalConnectionType.google) |
| 621 | + |
| 622 | + # Both subscribers are connecting the same Google calendar |
| 623 | + shared_calendar_url = 'shared-calendar@google.com' |
| 624 | + |
| 625 | + calendar_data_a = schemas.CalendarConnection( |
| 626 | + title='Calendar for Subscriber A', |
| 627 | + color='#4285f4', |
| 628 | + provider=models.CalendarProvider.google, |
| 629 | + url=shared_calendar_url, |
| 630 | + user=shared_calendar_url, |
| 631 | + password='', |
| 632 | + ) |
| 633 | + |
| 634 | + calendar_data_b = schemas.CalendarConnection( |
| 635 | + title='Calendar for Subscriber B', |
| 636 | + color='#ff0000', |
| 637 | + provider=models.CalendarProvider.google, |
| 638 | + url=shared_calendar_url, |
| 639 | + user=shared_calendar_url, |
| 640 | + password='', |
| 641 | + ) |
| 642 | + |
| 643 | + with with_db() as db: |
| 644 | + # Subscriber A connects the calendar first |
| 645 | + calendar_a = repo.calendar.update_or_create( |
| 646 | + db=db, |
| 647 | + calendar=calendar_data_a, |
| 648 | + calendar_url=shared_calendar_url, |
| 649 | + subscriber_id=subscriber_a.id, |
| 650 | + external_connection_id=ec_a.id, |
| 651 | + ) |
| 652 | + |
| 653 | + assert calendar_a is not None |
| 654 | + assert calendar_a.owner_id == subscriber_a.id |
| 655 | + |
| 656 | + # Subscriber B connects the same calendar |
| 657 | + calendar_b = repo.calendar.update_or_create( |
| 658 | + db=db, |
| 659 | + calendar=calendar_data_b, |
| 660 | + calendar_url=shared_calendar_url, |
| 661 | + subscriber_id=subscriber_b.id, |
| 662 | + external_connection_id=ec_b.id, |
| 663 | + ) |
| 664 | + |
| 665 | + # Subscriber B should get their own calendar record |
| 666 | + assert calendar_b is not None |
| 667 | + assert calendar_b.owner_id == subscriber_b.id |
| 668 | + assert calendar_b.id != calendar_a.id # Different calendar records |
| 669 | + |
| 670 | + # Verify each subscriber has exactly one calendar |
| 671 | + calendars_a = repo.calendar.get_by_subscriber(db, subscriber_a.id) |
| 672 | + calendars_b = repo.calendar.get_by_subscriber(db, subscriber_b.id) |
| 673 | + |
| 674 | + assert len(calendars_a) == 1 |
| 675 | + assert len(calendars_b) == 1 |
| 676 | + |
| 677 | + # Verify the calendars belong to the correct subscribers |
| 678 | + assert calendars_a[0].owner_id == subscriber_a.id |
| 679 | + assert calendars_b[0].owner_id == subscriber_b.id |
| 680 | + |
| 681 | + # Verify the external connections are correct |
| 682 | + assert calendars_a[0].external_connection_id == ec_a.id |
| 683 | + assert calendars_b[0].external_connection_id == ec_b.id |
| 684 | + |
| 685 | + def test_update_or_create_does_not_modify_other_subscribers_calendar( |
| 686 | + self, with_db, make_pro_subscriber, make_external_connections |
| 687 | + ): |
| 688 | + """Test that when Subscriber B connects a calendar that Subscriber A already has, |
| 689 | + Subscriber A's calendar is not modified.""" |
| 690 | + subscriber_a = make_pro_subscriber() |
| 691 | + subscriber_b = make_pro_subscriber() |
| 692 | + |
| 693 | + ec_a = make_external_connections(subscriber_a.id, type=models.ExternalConnectionType.google) |
| 694 | + ec_b = make_external_connections(subscriber_b.id, type=models.ExternalConnectionType.google) |
| 695 | + |
| 696 | + shared_calendar_url = 'shared-calendar@google.com' |
| 697 | + |
| 698 | + calendar_data_a = schemas.CalendarConnection( |
| 699 | + title='Original Title A', |
| 700 | + color='#4285f4', |
| 701 | + provider=models.CalendarProvider.google, |
| 702 | + url=shared_calendar_url, |
| 703 | + user=shared_calendar_url, |
| 704 | + password='', |
| 705 | + ) |
| 706 | + |
| 707 | + calendar_data_b = schemas.CalendarConnection( |
| 708 | + title='Title B', |
| 709 | + color='#ff0000', |
| 710 | + provider=models.CalendarProvider.google, |
| 711 | + url=shared_calendar_url, |
| 712 | + user=shared_calendar_url, |
| 713 | + password='', |
| 714 | + ) |
| 715 | + |
| 716 | + with with_db() as db: |
| 717 | + # Subscriber A connects first |
| 718 | + repo.calendar.update_or_create( |
| 719 | + db=db, |
| 720 | + calendar=calendar_data_a, |
| 721 | + calendar_url=shared_calendar_url, |
| 722 | + subscriber_id=subscriber_a.id, |
| 723 | + external_connection_id=ec_a.id, |
| 724 | + ) |
| 725 | + |
| 726 | + # Subscriber B connects the same calendar |
| 727 | + repo.calendar.update_or_create( |
| 728 | + db=db, |
| 729 | + calendar=calendar_data_b, |
| 730 | + calendar_url=shared_calendar_url, |
| 731 | + subscriber_id=subscriber_b.id, |
| 732 | + external_connection_id=ec_b.id, |
| 733 | + ) |
| 734 | + |
| 735 | + # Verify Subscriber A's calendar was not modified |
| 736 | + calendars_a = repo.calendar.get_by_subscriber(db, subscriber_a.id) |
| 737 | + assert len(calendars_a) == 1 |
| 738 | + assert calendars_a[0].title == 'Original Title A' |
| 739 | + assert calendars_a[0].color == '#4285f4' |
| 740 | + assert calendars_a[0].external_connection_id == ec_a.id |
0 commit comments