|
20 | 20 | LearningPackage, |
21 | 21 | PublishableEntity, |
22 | 22 | PublishLog, |
| 23 | + PublishLogRecord, |
23 | 24 | ) |
24 | 25 |
|
25 | 26 | User = get_user_model() |
@@ -935,6 +936,56 @@ def test_simple_publish_log(self) -> None: |
935 | 936 | assert e1_pub_record.old_version == entity1_v1 |
936 | 937 | assert e1_pub_record.new_version == entity1_v2 |
937 | 938 |
|
| 939 | + def test_publish_all_drafts_sets_direct_true(self) -> None: |
| 940 | + """publish_all_drafts() marks every PublishLogRecord as direct=True.""" |
| 941 | + entity_1 = publishing_api.create_publishable_entity( |
| 942 | + self.learning_package_1.id, "direct_entity_1", |
| 943 | + created=self.now, created_by=None, |
| 944 | + ) |
| 945 | + publishing_api.create_publishable_entity_version( |
| 946 | + entity_1.id, version_num=1, title="Direct Entity 1", |
| 947 | + created=self.now, created_by=None, |
| 948 | + ) |
| 949 | + entity_2 = publishing_api.create_publishable_entity( |
| 950 | + self.learning_package_1.id, "direct_entity_2", |
| 951 | + created=self.now, created_by=None, |
| 952 | + ) |
| 953 | + publishing_api.create_publishable_entity_version( |
| 954 | + entity_2.id, version_num=1, title="Direct Entity 2", |
| 955 | + created=self.now, created_by=None, |
| 956 | + ) |
| 957 | + publish_log = publishing_api.publish_all_drafts(self.learning_package_1.id) |
| 958 | + assert publish_log.records.get(entity=entity_1).direct is True |
| 959 | + assert publish_log.records.get(entity=entity_2).direct is True |
| 960 | + |
| 961 | + def test_publish_from_drafts_sets_direct_true(self) -> None: |
| 962 | + """An explicitly selected entity in publish_from_drafts() gets direct=True.""" |
| 963 | + entity = publishing_api.create_publishable_entity( |
| 964 | + self.learning_package_1.id, "explicit_entity", |
| 965 | + created=self.now, created_by=None, |
| 966 | + ) |
| 967 | + publishing_api.create_publishable_entity_version( |
| 968 | + entity.id, version_num=1, title="Explicit Entity", |
| 969 | + created=self.now, created_by=None, |
| 970 | + ) |
| 971 | + publish_log = publishing_api.publish_from_drafts( |
| 972 | + self.learning_package_1.id, |
| 973 | + Draft.objects.filter(entity=entity), |
| 974 | + ) |
| 975 | + assert publish_log.records.get(entity=entity).direct is True |
| 976 | + |
| 977 | + def test_publish_log_record_direct_defaults_to_false(self) -> None: |
| 978 | + """ |
| 979 | + New PublishLogRecords default to direct=False (not None). |
| 980 | +
|
| 981 | + None is reserved for historical records that pre-date the direct field |
| 982 | + (set via the backfill data migration). Records created by the |
| 983 | + application—e.g. side-effect records in _create_side_effects_for_change_log() |
| 984 | + that don't explicitly set direct—should get False, not None. |
| 985 | + """ |
| 986 | + field = PublishLogRecord._meta.get_field('direct') |
| 987 | + assert field.default is False |
| 988 | + |
938 | 989 |
|
939 | 990 | class EntitiesQueryTestCase(TestCase): |
940 | 991 | """ |
@@ -1429,6 +1480,158 @@ def test_publish_all_layers(self) -> None: |
1429 | 1480 | # the publish log records. |
1430 | 1481 | assert publish_log.records.count() == 3 |
1431 | 1482 |
|
| 1483 | + def test_direct_field_publishing_container_marks_dependencies_indirect(self) -> None: |
| 1484 | + """ |
| 1485 | + Publishing a Unit explicitly marks the Unit as direct=True and its |
| 1486 | + unpublished Component dependency as direct=False. |
| 1487 | + """ |
| 1488 | + component = publishing_api.create_publishable_entity( |
| 1489 | + self.learning_package.id, "direct_component", |
| 1490 | + created=self.now, created_by=None, |
| 1491 | + ) |
| 1492 | + publishing_api.create_publishable_entity_version( |
| 1493 | + component.id, version_num=1, title="Direct Component", |
| 1494 | + created=self.now, created_by=None, |
| 1495 | + ) |
| 1496 | + unit = containers_api.create_container( |
| 1497 | + self.learning_package.id, "direct_unit", |
| 1498 | + created=self.now, created_by=None, container_cls=TestContainer, |
| 1499 | + ) |
| 1500 | + containers_api.create_container_version( |
| 1501 | + unit.id, 1, title="Direct Unit", entities=[component], |
| 1502 | + created=self.now, created_by=None, |
| 1503 | + ) |
| 1504 | + publish_log = publishing_api.publish_from_drafts( |
| 1505 | + self.learning_package.id, |
| 1506 | + Draft.objects.filter(entity=unit.publishable_entity), |
| 1507 | + ) |
| 1508 | + assert publish_log.records.get(entity=unit.publishable_entity).direct is True |
| 1509 | + assert publish_log.records.get(entity=component).direct is False |
| 1510 | + |
| 1511 | + def test_direct_field_unit_no_version_change_still_direct_true(self) -> None: |
| 1512 | + """ |
| 1513 | + Publishing a Unit that has no version change of its own (draft version |
| 1514 | + == published version) still marks the Unit's record as direct=True. |
| 1515 | +
|
| 1516 | + The user explicitly selected the Unit to publish, so it gets direct=True |
| 1517 | + even though the only actual change is in its Component child. The Unit's |
| 1518 | + record has old_version == new_version (pure side-effect in terms of |
| 1519 | + versioning), but user intent was directed at the Unit. |
| 1520 | + """ |
| 1521 | + component = publishing_api.create_publishable_entity( |
| 1522 | + self.learning_package.id, "no_change_component", |
| 1523 | + created=self.now, created_by=None, |
| 1524 | + ) |
| 1525 | + component_v1 = publishing_api.create_publishable_entity_version( |
| 1526 | + component.id, version_num=1, title="No-change Component", |
| 1527 | + created=self.now, created_by=None, |
| 1528 | + ) |
| 1529 | + unit = containers_api.create_container( |
| 1530 | + self.learning_package.id, "no_change_unit", |
| 1531 | + created=self.now, created_by=None, container_cls=TestContainer, |
| 1532 | + ) |
| 1533 | + unit_v1 = containers_api.create_container_version( |
| 1534 | + unit.id, 1, title="No-change Unit", entities=[component], |
| 1535 | + created=self.now, created_by=None, |
| 1536 | + ) |
| 1537 | + # Initial publish so both Unit and Component have a published version. |
| 1538 | + publishing_api.publish_from_drafts( |
| 1539 | + self.learning_package.id, |
| 1540 | + Draft.objects.filter(entity=unit.publishable_entity), |
| 1541 | + ) |
| 1542 | + |
| 1543 | + # Create a new Component version. The Unit's draft stays at unit_v1, |
| 1544 | + # but its dependencies_hash_digest now differs from the published state. |
| 1545 | + publishing_api.create_publishable_entity_version( |
| 1546 | + component.id, version_num=2, title="No-change Component v2", |
| 1547 | + created=self.now, created_by=None, |
| 1548 | + ) |
| 1549 | + |
| 1550 | + # Publish the Unit explicitly. The Unit has no version change of its |
| 1551 | + # own (old_version == new_version == unit_v1). |
| 1552 | + publish_log = publishing_api.publish_from_drafts( |
| 1553 | + self.learning_package.id, |
| 1554 | + Draft.objects.filter(entity=unit.publishable_entity), |
| 1555 | + ) |
| 1556 | + unit_record = publish_log.records.get(entity=unit.publishable_entity) |
| 1557 | + component_record = publish_log.records.get(entity=component) |
| 1558 | + |
| 1559 | + # User selected the Unit → direct=True despite no version change. |
| 1560 | + assert unit_record.direct is True |
| 1561 | + assert unit_record.old_version_id == unit_v1.pk |
| 1562 | + assert unit_record.new_version_id == unit_v1.pk |
| 1563 | + |
| 1564 | + # Component was pulled in as a dependency → direct=False. |
| 1565 | + assert component_record.direct is False |
| 1566 | + assert component_record.old_version == component_v1 |
| 1567 | + assert component_record.new_version != component_v1 |
| 1568 | + |
| 1569 | + def test_direct_field_publishing_component_marks_parent_indirect(self) -> None: |
| 1570 | + """ |
| 1571 | + Publishing a Component directly marks the Component as direct=True. |
| 1572 | + The parent Unit also gets a PublishLogRecord (because it has an unpinned |
| 1573 | + reference to the Component and its dependencies_hash_digest now differs |
| 1574 | + from the published state) with direct=False. |
| 1575 | + """ |
| 1576 | + component = publishing_api.create_publishable_entity( |
| 1577 | + self.learning_package.id, "leaf_component", |
| 1578 | + created=self.now, created_by=None, |
| 1579 | + ) |
| 1580 | + publishing_api.create_publishable_entity_version( |
| 1581 | + component.id, version_num=1, title="Leaf Component", |
| 1582 | + created=self.now, created_by=None, |
| 1583 | + ) |
| 1584 | + unit = containers_api.create_container( |
| 1585 | + self.learning_package.id, "leaf_unit", |
| 1586 | + created=self.now, created_by=None, container_cls=TestContainer, |
| 1587 | + ) |
| 1588 | + containers_api.create_container_version( |
| 1589 | + unit.id, 1, title="Leaf Unit", entities=[component], |
| 1590 | + created=self.now, created_by=None, |
| 1591 | + ) |
| 1592 | + # First publish everything to establish a published baseline for the Unit |
| 1593 | + publishing_api.publish_all_drafts(self.learning_package.id) |
| 1594 | + |
| 1595 | + # Create a new component version so it has unpublished changes |
| 1596 | + publishing_api.create_publishable_entity_version( |
| 1597 | + component.id, version_num=2, title="Leaf Component v2", |
| 1598 | + created=self.now, created_by=None, |
| 1599 | + ) |
| 1600 | + publish_log = publishing_api.publish_from_drafts( |
| 1601 | + self.learning_package.id, |
| 1602 | + Draft.objects.filter(entity=component), |
| 1603 | + ) |
| 1604 | + assert publish_log.records.get(entity=component).direct is True |
| 1605 | + assert publish_log.records.get(entity=unit.publishable_entity).direct is False |
| 1606 | + |
| 1607 | + def test_direct_field_both_selected_both_direct(self) -> None: |
| 1608 | + """ |
| 1609 | + When both a Unit and its Component are explicitly selected, both |
| 1610 | + get direct=True even though Component is also a dependency of Unit. |
| 1611 | + """ |
| 1612 | + component = publishing_api.create_publishable_entity( |
| 1613 | + self.learning_package.id, "both_component", |
| 1614 | + created=self.now, created_by=None, |
| 1615 | + ) |
| 1616 | + publishing_api.create_publishable_entity_version( |
| 1617 | + component.id, version_num=1, title="Both Component", |
| 1618 | + created=self.now, created_by=None, |
| 1619 | + ) |
| 1620 | + unit = containers_api.create_container( |
| 1621 | + self.learning_package.id, "both_unit", |
| 1622 | + created=self.now, created_by=None, container_cls=TestContainer, |
| 1623 | + ) |
| 1624 | + containers_api.create_container_version( |
| 1625 | + unit.id, 1, title="Both Unit", entities=[component], |
| 1626 | + created=self.now, created_by=None, |
| 1627 | + ) |
| 1628 | + publish_log = publishing_api.publish_from_drafts( |
| 1629 | + self.learning_package.id, |
| 1630 | + Draft.objects.filter(entity__in=[component, unit.publishable_entity]), |
| 1631 | + ) |
| 1632 | + assert publish_log.records.get(entity=component).direct is True |
| 1633 | + assert publish_log.records.get(entity=unit.publishable_entity).direct is True |
| 1634 | + |
1432 | 1635 | def test_container_next_version(self) -> None: |
1433 | 1636 | """Test that next_version works for containers.""" |
1434 | 1637 | child_1 = publishing_api.create_publishable_entity( |
|
0 commit comments