diff --git a/backend/apps/accounts/tests_managed_by.py b/backend/apps/accounts/tests_managed_by.py index e52f1c0..8dd2485 100644 --- a/backend/apps/accounts/tests_managed_by.py +++ b/backend/apps/accounts/tests_managed_by.py @@ -8,13 +8,13 @@ class ManagedByRequestTests(TestCase): def setUp(self): self.client = APIClient() self.buh = User.objects.create_user( - user_name="buh1", password="x", status=User.STATUS_BUH + user_login="buh1", password="x", status=User.STATUS_BUH ) self.buh_other = User.objects.create_user( - user_name="buh2", password="x", status=User.STATUS_BUH + user_login="buh2", password="x", status=User.STATUS_BUH ) self.gm = User.objects.create_user( - user_name="gm1", password="x", status=User.STATUS_GM + user_login="gm1", password="x", status=User.STATUS_GM ) def test_gm_requests_and_buh_approves(self): @@ -60,7 +60,7 @@ class ManagedByRequestTests(TestCase): def test_gm_lists_buhs_only(self): User.objects.create_user( - user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN + user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN ) self.client.force_authenticate(self.gm) response = self.client.get("/api/v1/users/buhs/") @@ -70,7 +70,7 @@ class ManagedByRequestTests(TestCase): def test_gm_admin_cannot_request_buh(self): gm_admin = User.objects.create_user( - user_name="gma1", password="x", status=User.STATUS_GM_ADMIN + user_login="gma1", password="x", status=User.STATUS_GM_ADMIN ) self.client.force_authenticate(gm_admin) response = self.client.post( @@ -80,7 +80,7 @@ class ManagedByRequestTests(TestCase): def test_gm_cannot_request_bu_admin(self): bu_admin = User.objects.create_user( - user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN + user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN ) self.client.force_authenticate(self.gm) response = self.client.post( @@ -90,7 +90,7 @@ class ManagedByRequestTests(TestCase): def test_bu_admin_cannot_approve(self): bu_admin = User.objects.create_user( - user_name="buadmin1", password="x", status=User.STATUS_BU_ADMIN + user_login="buadmin1", password="x", status=User.STATUS_BU_ADMIN ) self.gm.managed_by_pending = bu_admin self.gm.save(update_fields=["managed_by_pending", "updated_at"]) diff --git a/backend/apps/accounts/tests_scoping.py b/backend/apps/accounts/tests_scoping.py index ada55f2..c3049de 100644 --- a/backend/apps/accounts/tests_scoping.py +++ b/backend/apps/accounts/tests_scoping.py @@ -10,19 +10,19 @@ class VisibilityScopeTests(TestCase): def setUp(self): self.client = APIClient() self.director = User.objects.create_user( - user_name="dir1", password="x", status=User.STATUS_DIRECTOR + user_login="dir1", password="x", status=User.STATUS_DIRECTOR ) self.buh = User.objects.create_user( - user_name="buh1", password="x", status=User.STATUS_BUH + user_login="buh1", password="x", status=User.STATUS_BUH ) self.buh_other = User.objects.create_user( - user_name="buh2", password="x", status=User.STATUS_BUH + user_login="buh2", password="x", status=User.STATUS_BUH ) self.gm = User.objects.create_user( - user_name="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh + user_login="gm1", password="x", status=User.STATUS_GM, managed_by=self.buh ) self.gm_other = User.objects.create_user( - user_name="gm2", + user_login="gm2", password="x", status=User.STATUS_GM, managed_by=self.buh_other, diff --git a/backend/apps/farms/tests.py b/backend/apps/farms/tests.py index c8b956b..cbba045 100644 --- a/backend/apps/farms/tests.py +++ b/backend/apps/farms/tests.py @@ -7,18 +7,32 @@ from rest_framework.test import APIClient from apps.accounts.models import User from apps.farms.models import Cycle, Flock, Kandang, Site from apps.operations.models import FeedSacks, ManualInput +from apps.sync.models import CitySite + + +def _gm_visible_site(*, owner: User, site_name: str, code: str) -> Site: + """GM scope only sees sites linked to an active CitySite they registered.""" + city = CitySite.objects.create( + code=code, + name=site_name, + api_base_url=f"https://{code}.example/api/v1", + registered_by=owner, + ) + return Site.objects.create(site_name=site_name, user=owner, city_site=city) class FarmAPITests(TestCase): def setUp(self): - self.user = User.objects.create_user(user_name="tester", password="secret") + self.user = User.objects.create_user(user_login="tester", password="secret") self.admin = User.objects.create_user( - user_name="admin", + user_login="admin", password="secret", status=User.STATUS_DIRECTOR_ADMIN, ) self.client = APIClient() - self.site = Site.objects.create(site_name="Sukawarna", user=self.user) + self.site = _gm_visible_site( + owner=self.user, site_name="Sukawarna", code="sukawarna" + ) self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=self.site) def test_create_flock_under_kandang_requires_director_admin(self): @@ -73,12 +87,9 @@ class FarmAPITests(TestCase): doc_in_count=9000, status=Cycle.STATUS_ACTIVE, ) - new_end = start + timedelta(days=39) response = self.client.patch( f"/api/v1/cycles/{cycle.pk}/", { - "end_date": new_end.isoformat(), - "total_days": 40, "doc_in_count": 9500, "chick_in_weight": 42, }, @@ -86,8 +97,8 @@ class FarmAPITests(TestCase): ) self.assertEqual(response.status_code, 200, response.data) cycle.refresh_from_db() - self.assertEqual(cycle.end_date, new_end) - self.assertEqual(cycle.total_days, 40) + # end_date is not writable via PATCH (close flow uses approve-close). + self.assertEqual(cycle.end_date, start + timedelta(days=34)) self.assertEqual(cycle.doc_in_count, 9500) self.assertEqual(cycle.doc_in_weight, 42) @@ -113,8 +124,8 @@ class FarmAPITests(TestCase): self.assertEqual(cycle.status, Cycle.STATUS_ACTIVE) def test_gm_can_approve_cycle_close(self): - gm = User.objects.create_user(user_name="gm_approver", password="x", status=User.STATUS_GM) - site = Site.objects.create(site_name="GM Site", user=gm) + gm = User.objects.create_user(user_login="gm_approver", password="x", status=User.STATUS_GM) + site = _gm_visible_site(owner=gm, site_name="GM Site", code="gm-approve") kandang = Kandang.objects.create(kandang_name="Kandang GM", site=site) self.client.force_authenticate(gm) start = date(2026, 8, 1) @@ -143,8 +154,8 @@ class FarmAPITests(TestCase): self.assertEqual(cycle.end_date, proposed) def test_gm_can_reject_cycle_close(self): - gm = User.objects.create_user(user_name="gm_rejector", password="x", status=User.STATUS_GM) - site = Site.objects.create(site_name="GM Reject Site", user=gm) + gm = User.objects.create_user(user_login="gm_rejector", password="x", status=User.STATUS_GM) + site = _gm_visible_site(owner=gm, site_name="GM Reject Site", code="gm-reject") kandang = Kandang.objects.create(kandang_name="Kandang R", site=site) self.client.force_authenticate(gm) start = date(2026, 8, 1) @@ -170,9 +181,11 @@ class FarmAPITests(TestCase): def test_gm_admin_cannot_approve_cycle_close(self): gm_admin = User.objects.create_user( - user_name="gm_admin_user", password="x", status=User.STATUS_GM_ADMIN + user_login="gm_admin_user", password="x", status=User.STATUS_GM_ADMIN + ) + site = _gm_visible_site( + owner=gm_admin, site_name="GM Admin Site", code="gm-admin-site" ) - site = Site.objects.create(site_name="GM Admin Site", user=gm_admin) kandang = Kandang.objects.create(kandang_name="Kandang GMA", site=site) self.client.force_authenticate(gm_admin) start = date(2026, 8, 1) diff --git a/backend/apps/jobs/management/commands/bootstrap_admin.py b/backend/apps/jobs/management/commands/bootstrap_admin.py index 058b810..ef5cf80 100644 --- a/backend/apps/jobs/management/commands/bootstrap_admin.py +++ b/backend/apps/jobs/management/commands/bootstrap_admin.py @@ -7,7 +7,7 @@ from apps.accounts.models import ApiKey, User def ensure_bootstrap_user( *, - user_name: str, + user_login: str, password: str, status: str, is_superuser: bool, @@ -15,8 +15,9 @@ def ensure_bootstrap_user( stdout, ) -> User: user, created = User.objects.get_or_create( - user_name=user_name, + user_login=user_login, defaults={ + "user_name": user_login, "status": status, "is_superuser": is_superuser, }, @@ -24,7 +25,7 @@ def ensure_bootstrap_user( if not created and user.status != status: stdout.write( - f"Updating {user_name!r} status {user.status!r} → {status!r}" + f"Updating {user_login!r} status {user.status!r} → {status!r}" ) should_set_password = created or not user.has_usable_password() or force_password @@ -34,12 +35,12 @@ def ensure_bootstrap_user( user.is_superuser = is_superuser user.save() action = "Created" if created else "Updated password for" - stdout.write(f"{action} {status} user {user_name}") + stdout.write(f"{action} {status} user {user_login}") else: user.status = status user.is_superuser = is_superuser user.save(update_fields=["status", "is_superuser", "updated_at"]) - stdout.write(f"User {user_name} already exists (password unchanged)") + stdout.write(f"User {user_login} already exists (password unchanged)") return user @@ -70,7 +71,7 @@ class Command(BaseCommand): ) admin = ensure_bootstrap_user( - user_name=admin_user_name, + user_login=admin_user_name, password=admin_password, status=User.STATUS_SUPERUSER, is_superuser=True, @@ -90,7 +91,7 @@ class Command(BaseCommand): "BOOTSTRAP_STAFF_PASSWORD is required when BOOTSTRAP_STAFF_USER is set." ) ensure_bootstrap_user( - user_name=staff_user_name, + user_login=staff_user_name, password=staff_password, status=User.STATUS_GM, is_superuser=False, diff --git a/backend/apps/jobs/management/commands/seed_demo.py b/backend/apps/jobs/management/commands/seed_demo.py index 1cba028..4a00d05 100644 --- a/backend/apps/jobs/management/commands/seed_demo.py +++ b/backend/apps/jobs/management/commands/seed_demo.py @@ -75,8 +75,11 @@ class Command(BaseCommand): defaults["managed_by"] = managed_by user, created = User.objects.get_or_create( - user_name=spec["user_name"], - defaults=defaults, + user_login=spec["user_name"], + defaults={ + **defaults, + "user_name": spec["user_name"], + }, ) user.status = spec["status"] user.is_superuser = spec.get("is_superuser", False) @@ -98,7 +101,10 @@ class Command(BaseCommand): return user def _remove_legacy_staff(self, gm: User) -> None: - staff = User.objects.filter(user_name="staff").first() + staff = ( + User.objects.filter(user_login="staff").first() + or User.objects.filter(user_name="staff").first() + ) if not staff: return moved_sites = Site.objects.filter(user=staff).update(user=gm) diff --git a/backend/apps/jobs/tests.py b/backend/apps/jobs/tests.py index 053862b..cf94dc8 100644 --- a/backend/apps/jobs/tests.py +++ b/backend/apps/jobs/tests.py @@ -15,7 +15,8 @@ from django.core.management import call_command class BootstrapAdminTests(TestCase): def test_creates_break_glass_superuser_without_demo_data(self): call_command("bootstrap_admin") - user = User.objects.get(user_name="admin") + user = User.objects.get(user_login="admin") + self.assertEqual(user.user_name, "admin") self.assertEqual(user.status, User.STATUS_SUPERUSER) self.assertTrue(user.is_superuser) self.assertTrue(user.check_password("Pr04dm1n")) @@ -33,7 +34,8 @@ class BootstrapAdminTests(TestCase): ) def test_creates_gm_when_configured(self): call_command("bootstrap_admin") - staff = User.objects.get(user_name="staff") + staff = User.objects.get(user_login="staff") + self.assertEqual(staff.user_name, "staff") self.assertEqual(staff.status, User.STATUS_GM) self.assertFalse(staff.is_superuser) self.assertTrue(staff.check_password("St4ff!")) diff --git a/backend/apps/operations/tests.py b/backend/apps/operations/tests.py index 54566a8..95a17e6 100644 --- a/backend/apps/operations/tests.py +++ b/backend/apps/operations/tests.py @@ -12,7 +12,7 @@ from apps.operations.models import ChickenWeight, FeedSacks, IotPanel @override_settings(DASHBOARD_PUBLISH_HOUR=0) class OperationsAPITests(TestCase): def setUp(self): - self.user = User.objects.create_user(user_name="tester", password="secret") + self.user = User.objects.create_user(user_login="tester", password="secret") self.client = APIClient() self.client.force_authenticate(self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user) @@ -311,7 +311,7 @@ class OperationsAPITests(TestCase): @override_settings(DASHBOARD_PUBLISH_HOUR=17, TIME_ZONE="Asia/Jakarta") class DashboardVisibilityTests(TestCase): def setUp(self): - self.user = User.objects.create_user(user_name="tester", password="secret") + self.user = User.objects.create_user(user_login="tester", password="secret") self.client = APIClient() self.client.force_authenticate(self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user) diff --git a/backend/apps/operations/tests_chicken_counting_edge.py b/backend/apps/operations/tests_chicken_counting_edge.py index 9e1fe27..8892979 100644 --- a/backend/apps/operations/tests_chicken_counting_edge.py +++ b/backend/apps/operations/tests_chicken_counting_edge.py @@ -62,7 +62,7 @@ class AggregationTests(TestCase): ) class SyncChickenCountingTests(TestCase): def setUp(self): - user = User.objects.create_user(user_name="sync-user", password="secret") + user = User.objects.create_user(user_login="sync-user", password="secret") site = Site.objects.create(site_name="Sukawarna", user=user) self.kandang = Kandang.objects.create(kandang_name="Kandang 1", site=site) self.cycle = Cycle.objects.create( @@ -130,7 +130,7 @@ class SyncChickenCountingTests(TestCase): ) class ChickenCountingSyncApiTests(TestCase): def setUp(self): - self.user = User.objects.create_user(user_name="tester", password="secret") + self.user = User.objects.create_user(user_login="tester", password="secret") self.client = APIClient() self.client.force_authenticate(self.user) site = Site.objects.create(site_name="Sukawarna", user=self.user) diff --git a/backend/apps/operations/tests_karung_web.py b/backend/apps/operations/tests_karung_web.py index 2044d44..a1ed513 100644 --- a/backend/apps/operations/tests_karung_web.py +++ b/backend/apps/operations/tests_karung_web.py @@ -156,7 +156,7 @@ class KarungWebCombinedTests(TestCase): @override_settings(KARUNG_WEB_ADMIN_BASE_URL="http://karung.internal") class RequestKarungTests(TestCase): def setUp(self): - user = User.objects.create_user(user_name="tester", password="secret") + user = User.objects.create_user(user_login="tester", password="secret") site = Site.objects.create(site_name="Sukawarna", user=user) self.kandang1 = Kandang.objects.create(kandang_name="Kandang 1", site=site) self.kandang2 = Kandang.objects.create(kandang_name="Kandang 2", site=site)