"""deploy_interactives must back up the host page before overwriting it.

Regression for the staging-bypass deep dive: the interactive HTML is staged
and preview-approved, but the host-page edit (iframe injection) called
update_page with no backup. A failed injection had no rollback point. The fix
backs up the pre-injection page body before the write.

#208: a failed backup used to be non-fatal — the deploy proceeded anyway,
leaving a live write with no rollback point. The fix aborts that page's
deploy entirely when the backup fails (update_page is never called).
"""

import os
import sys
import unittest
from unittest import mock

HERE = os.path.dirname(os.path.abspath(__file__))
SCRIPTS = os.path.join(HERE, "..", "scripts")
sys.path.insert(0, SCRIPTS)

import deploy_interactives as di  # noqa: E402


class BackupBeforeWriteTest(unittest.TestCase):
    def _run(self, save_backup_result):
        config = {"course_id": "123"}
        activities_by_page = {
            "m1-lesson": [
                {"filename": "m1-activity", "title": "Act", "page_type": "lesson"}
            ]
        }
        file_ids = {"m1": {"m1-activity": 999}}
        calls = []

        original_body = "<p>Original page body</p><hr>"

        with mock.patch.object(di, "get_page", return_value={"body": original_body}), \
             mock.patch.object(di, "find_injection_point", return_value=(len(original_body), "<hr>")), \
             mock.patch.object(di, "iframe_html", return_value="<iframe src='x'></iframe>"), \
             mock.patch.object(di.backup_manager, "save_backup",
                               side_effect=lambda *a, **k: (calls.append(("backup", a)) or save_backup_result)), \
             mock.patch.object(di, "update_page",
                               side_effect=lambda *a, **k: (calls.append(("update", a)) or True)) as upd:
            results = di.patch_pages(config, file_ids, activities_by_page)
        return calls, upd, results

    def test_backup_called_before_update_with_original_body(self):
        calls, upd, results = self._run({"ok": True, "path": "/tmp/x"})
        kinds = [c[0] for c in calls]
        self.assertIn("backup", kinds)
        self.assertIn("update", kinds)
        # Backup happens before the write.
        self.assertLess(kinds.index("backup"), kinds.index("update"))
        # Backup captured the ORIGINAL body (pre-injection).
        backup_args = next(c[1] for c in calls if c[0] == "backup")
        # save_backup(course_id, object_type, object_id, content, ...)
        self.assertEqual(backup_args[1], "pages")
        self.assertEqual(backup_args[2], "m1-lesson")
        self.assertEqual(backup_args[3], "<p>Original page body</p><hr>")
        # The pushed body is the injected one, not the backup.
        update_args = upd.call_args[0]
        self.assertIn("<iframe", update_args[2])
        self.assertTrue(results["m1-lesson"])

    def test_aborts_when_backup_fails(self):
        # Backup failure is fatal for this page (#208): never write a live
        # page with no rollback point. update_page must NOT be called, and
        # the page must be recorded as failed.
        calls, upd, results = self._run({"ok": False, "error": "boom"})
        kinds = [c[0] for c in calls]
        self.assertIn("backup", kinds)
        self.assertNotIn("update", kinds)
        upd.assert_not_called()
        self.assertFalse(results["m1-lesson"])


if __name__ == "__main__":
    unittest.main()
