campaign version refinment, user locks, db repair
This commit is contained in:
@@ -160,6 +160,67 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
self.assertEqual(sorted(bundle.namelist()), ["inbox/report-copy.txt", "inbox/report.txt"])
|
||||
self.assertEqual(bundle.read("inbox/report.txt"), b"first report")
|
||||
|
||||
def test_temporary_and_permanent_user_lock_lifecycle(self) -> None:
|
||||
headers, _ = self._login()
|
||||
created = self.client.post(
|
||||
"/api/v1/campaigns/new",
|
||||
headers=headers,
|
||||
json={"external_id": "lock-lifecycle", "name": "Lock lifecycle"},
|
||||
)
|
||||
self.assertEqual(created.status_code, 200, created.text)
|
||||
campaign_id = created.json()["campaign"]["id"]
|
||||
version_id = created.json()["version"]["id"]
|
||||
|
||||
temporary = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/lock-temporarily",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(temporary.status_code, 200, temporary.text)
|
||||
self.assertEqual(temporary.json()["user_lock_state"], "temporary")
|
||||
self.assertTrue(temporary.json()["user_locked_at"])
|
||||
|
||||
blocked_update = self.client.put(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}",
|
||||
headers=headers,
|
||||
json={"current_step": "fields"},
|
||||
)
|
||||
self.assertEqual(blocked_update.status_code, 409, blocked_update.text)
|
||||
|
||||
unlocked = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/unlock-user-lock",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(unlocked.status_code, 200, unlocked.text)
|
||||
self.assertIsNone(unlocked.json()["user_lock_state"])
|
||||
|
||||
updated = self.client.put(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}",
|
||||
headers=headers,
|
||||
json={"current_step": "fields"},
|
||||
)
|
||||
self.assertEqual(updated.status_code, 200, updated.text)
|
||||
self.assertEqual(updated.json()["current_step"], "fields")
|
||||
|
||||
relocked = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/lock-temporarily",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(relocked.status_code, 200, relocked.text)
|
||||
|
||||
permanent = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/lock-permanently",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(permanent.status_code, 200, permanent.text)
|
||||
self.assertEqual(permanent.json()["user_lock_state"], "permanent")
|
||||
self.assertTrue(permanent.json()["published_at"])
|
||||
|
||||
refused_unlock = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/unlock-user-lock",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(refused_unlock.status_code, 409, refused_unlock.text)
|
||||
|
||||
def test_campaign_create_validate_build_and_mock_send(self) -> None:
|
||||
headers, _ = self._login()
|
||||
campaign_json = {
|
||||
@@ -377,10 +438,59 @@ class ApiSmokeTests(unittest.TestCase):
|
||||
self.assertEqual(built.status_code, 200, built.text)
|
||||
self.assertEqual(built.json()["built_count"], 1)
|
||||
self.assertEqual(built.json()["messages"][0]["attachment_count"], 2)
|
||||
self.assertTrue(built.json().get("built_at"))
|
||||
self.assertTrue(built.json().get("build_token"))
|
||||
self.assertEqual(
|
||||
sum(len(item["managed_matches"]) for item in built.json()["messages"][0]["attachments"]),
|
||||
2,
|
||||
)
|
||||
resolved_paths = {
|
||||
match
|
||||
for attachment in built.json()["messages"][0]["attachments"]
|
||||
for match in attachment["matches"]
|
||||
}
|
||||
self.assertEqual(resolved_paths, {
|
||||
"invoices/archive/202605-010001-report.XLSX",
|
||||
"invoices/202605-010001-90100010-9601741.XLSX",
|
||||
})
|
||||
self.assertFalse(any("multimailer-managed-build" in value for value in resolved_paths))
|
||||
|
||||
jobs = self.client.get(
|
||||
f"/api/v1/campaigns/{campaign_id}/jobs",
|
||||
headers=headers,
|
||||
params={"version_id": version_id},
|
||||
)
|
||||
self.assertEqual(jobs.status_code, 200, jobs.text)
|
||||
self.assertEqual(len(jobs.json()["jobs"]), 1)
|
||||
job = jobs.json()["jobs"][0]
|
||||
self.assertEqual(job["campaign_version_id"], version_id)
|
||||
self.assertEqual(job["resolved_recipients"]["to"][0]["email"], "recipient@example.org")
|
||||
self.assertEqual(
|
||||
{
|
||||
match
|
||||
for attachment in job["attachments"]
|
||||
for match in attachment["matches"]
|
||||
},
|
||||
resolved_paths,
|
||||
)
|
||||
|
||||
review_state = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}/review-state",
|
||||
headers=headers,
|
||||
json={"inspection_complete": True, "reviewed_message_keys": ["recipient-1"]},
|
||||
)
|
||||
self.assertEqual(review_state.status_code, 200, review_state.text)
|
||||
stored_review = review_state.json()["editor_state"]["review_send"]
|
||||
self.assertTrue(stored_review["inspection_complete"])
|
||||
self.assertEqual(stored_review["reviewed_message_keys"], ["recipient-1"])
|
||||
self.assertEqual(stored_review["build_token"], built.json()["build_token"])
|
||||
|
||||
reloaded_version = self.client.get(
|
||||
f"/api/v1/campaigns/{campaign_id}/versions/{version_id}",
|
||||
headers=headers,
|
||||
)
|
||||
self.assertEqual(reloaded_version.status_code, 200, reloaded_version.text)
|
||||
self.assertTrue(reloaded_version.json()["editor_state"]["review_send"]["inspection_complete"])
|
||||
|
||||
mocked = self.client.post(
|
||||
f"/api/v1/campaigns/{campaign_id}/mock-send",
|
||||
|
||||
64
server/tests/test_database_migrations.py
Normal file
64
server/tests/test_database_migrations.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from alembic import command
|
||||
from alembic.runtime.migration import MigrationContext
|
||||
from sqlalchemy import create_engine, inspect
|
||||
|
||||
from app.db.base import Base
|
||||
from app.db.migrations import (
|
||||
REVISION_AUTH_RBAC,
|
||||
REVISION_FILE_FOLDERS,
|
||||
alembic_config,
|
||||
migrate_database,
|
||||
)
|
||||
|
||||
|
||||
class DatabaseMigrationTests(unittest.TestCase):
|
||||
def test_repairs_create_all_schema_drift_and_upgrades_to_head(self) -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="msm-migration-test-") as directory:
|
||||
database = Path(directory) / "legacy.db"
|
||||
url = f"sqlite:///{database}"
|
||||
|
||||
# Reproduce the historical development database: Alembic was run
|
||||
# through auth/RBAC, then create_all() created later file tables
|
||||
# without advancing alembic_version and without altering the
|
||||
# already-existing campaign_versions table.
|
||||
command.upgrade(alembic_config(database_url=url), REVISION_AUTH_RBAC)
|
||||
engine = create_engine(url)
|
||||
try:
|
||||
Base.metadata.create_all(bind=engine)
|
||||
with engine.connect() as connection:
|
||||
self.assertEqual(
|
||||
MigrationContext.configure(connection).get_current_revision(),
|
||||
REVISION_AUTH_RBAC,
|
||||
)
|
||||
self.assertIn("file_blobs", inspect(connection).get_table_names())
|
||||
self.assertNotIn(
|
||||
"user_lock_state",
|
||||
{column["name"] for column in inspect(connection).get_columns("campaign_versions")},
|
||||
)
|
||||
finally:
|
||||
engine.dispose()
|
||||
|
||||
result = migrate_database(database_url=url)
|
||||
self.assertEqual(result.previous_revision, REVISION_AUTH_RBAC)
|
||||
self.assertEqual(result.reconciled_revision, REVISION_FILE_FOLDERS)
|
||||
|
||||
engine = create_engine(url)
|
||||
try:
|
||||
with engine.connect() as connection:
|
||||
current = MigrationContext.configure(connection).get_current_revision()
|
||||
columns = {
|
||||
column["name"]
|
||||
for column in inspect(connection).get_columns("campaign_versions")
|
||||
}
|
||||
self.assertEqual(current, result.current_revision)
|
||||
self.assertIn("user_lock_state", columns)
|
||||
self.assertIn("user_locked_at", columns)
|
||||
self.assertIn("user_locked_by_user_id", columns)
|
||||
finally:
|
||||
engine.dispose()
|
||||
Reference in New Issue
Block a user