agentic-dev 0.2.9 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/README.md +23 -8
  2. package/bin/agentic-dev.mjs +692 -55
  3. package/lib/scaffold.mjs +109 -6
  4. package/package.json +1 -1
  5. package/client/admin/.dockerignore +0 -3
  6. package/client/admin/.env.example +0 -1
  7. package/client/admin/Dockerfile +0 -16
  8. package/client/admin/Dockerfile.dev +0 -18
  9. package/client/admin/README.md +0 -20
  10. package/client/admin/index.html +0 -12
  11. package/client/admin/package.json +0 -41
  12. package/client/admin/postcss.config.js +0 -6
  13. package/client/admin/scripts/ui-parity-admin-adapter.mjs +0 -65
  14. package/client/admin/src/api/alerts.ts +0 -33
  15. package/client/admin/src/api/client.ts +0 -71
  16. package/client/admin/src/api/orders.ts +0 -33
  17. package/client/admin/src/api/support.ts +0 -11
  18. package/client/admin/src/app/App.tsx +0 -23
  19. package/client/admin/src/auth/AuthProvider.tsx +0 -122
  20. package/client/admin/src/auth/ProtectedRoute.tsx +0 -22
  21. package/client/admin/src/auth/auth-client.ts +0 -38
  22. package/client/admin/src/auth/types.ts +0 -18
  23. package/client/admin/src/components/AdminNotificationsDrawer.tsx +0 -162
  24. package/client/admin/src/components/AdminShell.tsx +0 -76
  25. package/client/admin/src/components/ui/button.tsx +0 -34
  26. package/client/admin/src/components/ui/input.tsx +0 -21
  27. package/client/admin/src/lib/cn.ts +0 -6
  28. package/client/admin/src/lib/specRouteCatalog.json +0 -30
  29. package/client/admin/src/lib/specScreens.json +0 -22
  30. package/client/admin/src/main.tsx +0 -17
  31. package/client/admin/src/pages/AdminDashboardPage.tsx +0 -171
  32. package/client/admin/src/pages/AdminLoginPage.tsx +0 -75
  33. package/client/admin/src/pages/AdminQueuePage.tsx +0 -107
  34. package/client/admin/src/pages/AdminSupportPage.tsx +0 -61
  35. package/client/admin/src/styles/globals.css +0 -17
  36. package/client/admin/src/theme-vars.ts +0 -18
  37. package/client/admin/src/theme.ts +0 -25
  38. package/client/admin/src/vite-env.d.ts +0 -1
  39. package/client/admin/tailwind.config.js +0 -8
  40. package/client/admin/tsconfig.json +0 -25
  41. package/client/admin/vite.config.ts +0 -12
  42. package/client/landing/.dockerignore +0 -3
  43. package/client/landing/.env.example +0 -1
  44. package/client/landing/Dockerfile +0 -16
  45. package/client/landing/Dockerfile.dev +0 -18
  46. package/client/landing/README.md +0 -18
  47. package/client/landing/index.html +0 -12
  48. package/client/landing/package.json +0 -41
  49. package/client/landing/postcss.config.js +0 -6
  50. package/client/landing/scripts/ui-parity-landing-adapter.mjs +0 -65
  51. package/client/landing/src/App.tsx +0 -21
  52. package/client/landing/src/api/catalog.ts +0 -30
  53. package/client/landing/src/api/client.ts +0 -30
  54. package/client/landing/src/auth/AuthProvider.tsx +0 -122
  55. package/client/landing/src/auth/ProtectedRoute.tsx +0 -22
  56. package/client/landing/src/auth/auth-client.ts +0 -38
  57. package/client/landing/src/auth/types.ts +0 -18
  58. package/client/landing/src/components/LandingShell.tsx +0 -34
  59. package/client/landing/src/lib/specRouteCatalog.json +0 -23
  60. package/client/landing/src/lib/specScreens.json +0 -17
  61. package/client/landing/src/main.tsx +0 -17
  62. package/client/landing/src/pages/LandingHomePage.tsx +0 -215
  63. package/client/landing/src/pages/LandingLoginPage.tsx +0 -90
  64. package/client/landing/src/pages/LandingWorkspacePage.tsx +0 -126
  65. package/client/landing/src/styles/globals.css +0 -17
  66. package/client/landing/src/theme-vars.ts +0 -16
  67. package/client/landing/src/theme.ts +0 -21
  68. package/client/landing/src/vite-env.d.ts +0 -1
  69. package/client/landing/tailwind.config.js +0 -8
  70. package/client/landing/tsconfig.json +0 -25
  71. package/client/landing/vite.config.ts +0 -12
  72. package/client/mobile/.dockerignore +0 -2
  73. package/client/mobile/.env.example +0 -1
  74. package/client/mobile/Dockerfile +0 -16
  75. package/client/mobile/Dockerfile.dev +0 -18
  76. package/client/mobile/README.md +0 -19
  77. package/client/mobile/index.html +0 -12
  78. package/client/mobile/package.json +0 -42
  79. package/client/mobile/postcss.config.js +0 -6
  80. package/client/mobile/scripts/ui-parity-mobile-adapter.mjs +0 -67
  81. package/client/mobile/src/App.tsx +0 -1
  82. package/client/mobile/src/api/client.ts +0 -62
  83. package/client/mobile/src/api/fulfillment.ts +0 -55
  84. package/client/mobile/src/api/shipping.ts +0 -56
  85. package/client/mobile/src/app/App.tsx +0 -23
  86. package/client/mobile/src/auth/AuthProvider.tsx +0 -122
  87. package/client/mobile/src/auth/ProtectedRoute.tsx +0 -27
  88. package/client/mobile/src/auth/auth-client.ts +0 -38
  89. package/client/mobile/src/auth/types.ts +0 -18
  90. package/client/mobile/src/components/InShell.tsx +0 -74
  91. package/client/mobile/src/components/ui/button.tsx +0 -35
  92. package/client/mobile/src/components/ui/card.tsx +0 -15
  93. package/client/mobile/src/components/ui/input.tsx +0 -21
  94. package/client/mobile/src/lib/cn.ts +0 -6
  95. package/client/mobile/src/lib/specRouteCatalog.json +0 -26
  96. package/client/mobile/src/lib/specScreens.json +0 -22
  97. package/client/mobile/src/lib/useSpeechRecognitionInput.ts +0 -271
  98. package/client/mobile/src/main.tsx +0 -17
  99. package/client/mobile/src/pages/DashboardPage.tsx +0 -172
  100. package/client/mobile/src/pages/FulfillmentPage.tsx +0 -138
  101. package/client/mobile/src/pages/LoginPage.tsx +0 -74
  102. package/client/mobile/src/pages/ShippingPage.tsx +0 -338
  103. package/client/mobile/src/styles/globals.css +0 -23
  104. package/client/mobile/src/theme-vars.ts +0 -16
  105. package/client/mobile/src/theme.ts +0 -21
  106. package/client/mobile/src/vite-env.d.ts +0 -1
  107. package/client/mobile/tailwind.config.js +0 -8
  108. package/client/mobile/tsconfig.json +0 -25
  109. package/client/mobile/vite.config.ts +0 -12
  110. package/client/web/.dockerignore +0 -3
  111. package/client/web/.env.example +0 -1
  112. package/client/web/Dockerfile +0 -16
  113. package/client/web/Dockerfile.dev +0 -18
  114. package/client/web/README.md +0 -47
  115. package/client/web/index.html +0 -12
  116. package/client/web/package.json +0 -42
  117. package/client/web/postcss.config.js +0 -6
  118. package/client/web/scripts/ui-parity-web-adapter.mjs +0 -66
  119. package/client/web/src/api/client.ts +0 -30
  120. package/client/web/src/api/orders.ts +0 -42
  121. package/client/web/src/app/App.tsx +0 -21
  122. package/client/web/src/auth/AuthProvider.tsx +0 -122
  123. package/client/web/src/auth/ProtectedRoute.tsx +0 -22
  124. package/client/web/src/auth/auth-client.ts +0 -38
  125. package/client/web/src/auth/types.ts +0 -18
  126. package/client/web/src/components/AppShell.tsx +0 -59
  127. package/client/web/src/components/ui/button.tsx +0 -35
  128. package/client/web/src/components/ui/card.tsx +0 -7
  129. package/client/web/src/components/ui/input.tsx +0 -21
  130. package/client/web/src/lib/cn.ts +0 -6
  131. package/client/web/src/lib/specRouteCatalog.json +0 -23
  132. package/client/web/src/lib/specScreens.json +0 -17
  133. package/client/web/src/main.tsx +0 -17
  134. package/client/web/src/pages/DashboardPage.tsx +0 -158
  135. package/client/web/src/pages/LoginPage.tsx +0 -72
  136. package/client/web/src/pages/OrdersPage.tsx +0 -123
  137. package/client/web/src/styles/globals.css +0 -17
  138. package/client/web/src/theme-vars.ts +0 -18
  139. package/client/web/src/theme.ts +0 -25
  140. package/client/web/src/vite-env.d.ts +0 -1
  141. package/client/web/tailwind.config.js +0 -8
  142. package/client/web/tsconfig.json +0 -25
  143. package/client/web/vite.config.ts +0 -12
  144. package/server/.dockerignore +0 -4
  145. package/server/.env.example +0 -19
  146. package/server/Dockerfile +0 -22
  147. package/server/Dockerfile.dev +0 -19
  148. package/server/README.md +0 -33
  149. package/server/__init__.py +0 -0
  150. package/server/api/__init__.py +0 -1
  151. package/server/api/http/__init__.py +0 -4
  152. package/server/api/http/app.py +0 -53
  153. package/server/api/http/router.py +0 -24
  154. package/server/config.py +0 -52
  155. package/server/contexts/__init__.py +0 -12
  156. package/server/contexts/alerts/__init__.py +0 -1
  157. package/server/contexts/alerts/application/__init__.py +0 -13
  158. package/server/contexts/alerts/application/services.py +0 -41
  159. package/server/contexts/alerts/contracts/__init__.py +0 -3
  160. package/server/contexts/alerts/contracts/http/__init__.py +0 -3
  161. package/server/contexts/alerts/contracts/http/router.py +0 -37
  162. package/server/contexts/alerts/domain/__init__.py +0 -15
  163. package/server/contexts/alerts/domain/models.py +0 -29
  164. package/server/contexts/alerts/infrastructure/__init__.py +0 -11
  165. package/server/contexts/alerts/infrastructure/repository.py +0 -41
  166. package/server/contexts/auth/__init__.py +0 -1
  167. package/server/contexts/auth/application/__init__.py +0 -3
  168. package/server/contexts/auth/application/ports.py +0 -10
  169. package/server/contexts/auth/application/services.py +0 -64
  170. package/server/contexts/auth/contracts/__init__.py +0 -4
  171. package/server/contexts/auth/contracts/http/__init__.py +0 -4
  172. package/server/contexts/auth/contracts/http/dependencies.py +0 -37
  173. package/server/contexts/auth/contracts/http/router.py +0 -19
  174. package/server/contexts/auth/domain/__init__.py +0 -3
  175. package/server/contexts/auth/domain/models.py +0 -24
  176. package/server/contexts/auth/infrastructure/__init__.py +0 -4
  177. package/server/contexts/auth/infrastructure/adapters/memory.py +0 -19
  178. package/server/contexts/auth/infrastructure/adapters/mongodb.py +0 -24
  179. package/server/contexts/auth/infrastructure/adapters/sqlalchemy.py +0 -74
  180. package/server/contexts/auth/infrastructure/repository.py +0 -28
  181. package/server/contexts/catalog/__init__.py +0 -1
  182. package/server/contexts/catalog/application/__init__.py +0 -28
  183. package/server/contexts/catalog/application/ports.py +0 -15
  184. package/server/contexts/catalog/application/services.py +0 -154
  185. package/server/contexts/catalog/contracts/__init__.py +0 -3
  186. package/server/contexts/catalog/contracts/http/__init__.py +0 -3
  187. package/server/contexts/catalog/contracts/http/router.py +0 -60
  188. package/server/contexts/catalog/domain/__init__.py +0 -45
  189. package/server/contexts/catalog/domain/models.py +0 -113
  190. package/server/contexts/catalog/infrastructure/__init__.py +0 -4
  191. package/server/contexts/catalog/infrastructure/adapters/memory.py +0 -62
  192. package/server/contexts/catalog/infrastructure/repository.py +0 -8
  193. package/server/contexts/fulfillment/__init__.py +0 -1
  194. package/server/contexts/fulfillment/application/__init__.py +0 -13
  195. package/server/contexts/fulfillment/application/ports.py +0 -20
  196. package/server/contexts/fulfillment/application/services.py +0 -85
  197. package/server/contexts/fulfillment/contracts/__init__.py +0 -3
  198. package/server/contexts/fulfillment/contracts/http/__init__.py +0 -3
  199. package/server/contexts/fulfillment/contracts/http/router.py +0 -40
  200. package/server/contexts/fulfillment/domain/__init__.py +0 -25
  201. package/server/contexts/fulfillment/domain/models.py +0 -73
  202. package/server/contexts/fulfillment/infrastructure/__init__.py +0 -13
  203. package/server/contexts/fulfillment/infrastructure/adapters/memory.py +0 -43
  204. package/server/contexts/fulfillment/infrastructure/repository.py +0 -97
  205. package/server/contexts/health/__init__.py +0 -1
  206. package/server/contexts/health/application/__init__.py +0 -3
  207. package/server/contexts/health/application/services.py +0 -2
  208. package/server/contexts/health/contracts/__init__.py +0 -3
  209. package/server/contexts/health/contracts/http/__init__.py +0 -3
  210. package/server/contexts/health/contracts/http/router.py +0 -10
  211. package/server/contexts/inventory/__init__.py +0 -1
  212. package/server/contexts/inventory/application/__init__.py +0 -28
  213. package/server/contexts/inventory/application/ports.py +0 -11
  214. package/server/contexts/inventory/application/services.py +0 -214
  215. package/server/contexts/inventory/contracts/__init__.py +0 -3
  216. package/server/contexts/inventory/contracts/http/__init__.py +0 -3
  217. package/server/contexts/inventory/contracts/http/router.py +0 -82
  218. package/server/contexts/inventory/domain/__init__.py +0 -33
  219. package/server/contexts/inventory/domain/models.py +0 -93
  220. package/server/contexts/inventory/infrastructure/__init__.py +0 -4
  221. package/server/contexts/inventory/infrastructure/adapters/memory.py +0 -24
  222. package/server/contexts/inventory/infrastructure/repository.py +0 -8
  223. package/server/contexts/orders/__init__.py +0 -1
  224. package/server/contexts/orders/application/__init__.py +0 -19
  225. package/server/contexts/orders/application/services.py +0 -127
  226. package/server/contexts/orders/contracts/__init__.py +0 -3
  227. package/server/contexts/orders/contracts/http/__init__.py +0 -3
  228. package/server/contexts/orders/contracts/http/router.py +0 -82
  229. package/server/contexts/orders/domain/__init__.py +0 -29
  230. package/server/contexts/orders/domain/models.py +0 -95
  231. package/server/contexts/orders/infrastructure/__init__.py +0 -7
  232. package/server/contexts/orders/infrastructure/repository.py +0 -104
  233. package/server/contexts/shipping/__init__.py +0 -1
  234. package/server/contexts/shipping/application/__init__.py +0 -13
  235. package/server/contexts/shipping/application/services.py +0 -92
  236. package/server/contexts/shipping/contracts/__init__.py +0 -3
  237. package/server/contexts/shipping/contracts/http/__init__.py +0 -3
  238. package/server/contexts/shipping/contracts/http/router.py +0 -40
  239. package/server/contexts/shipping/domain/__init__.py +0 -19
  240. package/server/contexts/shipping/domain/models.py +0 -48
  241. package/server/contexts/shipping/infrastructure/__init__.py +0 -9
  242. package/server/contexts/shipping/infrastructure/repository.py +0 -50
  243. package/server/contexts/support/__init__.py +0 -1
  244. package/server/contexts/support/application/__init__.py +0 -13
  245. package/server/contexts/support/application/services.py +0 -29
  246. package/server/contexts/support/contracts/__init__.py +0 -3
  247. package/server/contexts/support/contracts/http/__init__.py +0 -3
  248. package/server/contexts/support/contracts/http/router.py +0 -40
  249. package/server/contexts/support/domain/__init__.py +0 -13
  250. package/server/contexts/support/domain/models.py +0 -27
  251. package/server/contexts/support/infrastructure/__init__.py +0 -11
  252. package/server/contexts/support/infrastructure/repository.py +0 -70
  253. package/server/contexts/user/__init__.py +0 -1
  254. package/server/contexts/user/application/__init__.py +0 -3
  255. package/server/contexts/user/application/ports.py +0 -11
  256. package/server/contexts/user/application/services.py +0 -44
  257. package/server/contexts/user/contracts/__init__.py +0 -3
  258. package/server/contexts/user/contracts/http/__init__.py +0 -3
  259. package/server/contexts/user/contracts/http/router.py +0 -26
  260. package/server/contexts/user/domain/__init__.py +0 -3
  261. package/server/contexts/user/domain/models.py +0 -22
  262. package/server/contexts/user/infrastructure/__init__.py +0 -3
  263. package/server/contexts/user/infrastructure/adapters/memory.py +0 -27
  264. package/server/contexts/user/infrastructure/adapters/mongodb.py +0 -41
  265. package/server/contexts/user/infrastructure/adapters/sqlalchemy.py +0 -94
  266. package/server/contexts/user/infrastructure/factory.py +0 -28
  267. package/server/data/README.md +0 -24
  268. package/server/data/bootstrap/alerts.json +0 -38
  269. package/server/data/bootstrap/auth_accounts.json +0 -18
  270. package/server/data/bootstrap/catalog_products.json +0 -179
  271. package/server/data/bootstrap/fulfillment_events.json +0 -5
  272. package/server/data/bootstrap/fulfillment_notes.json +0 -5
  273. package/server/data/bootstrap/fulfillment_tasks.json +0 -50
  274. package/server/data/bootstrap/inventory_levels.json +0 -80
  275. package/server/data/bootstrap/orders.json +0 -62
  276. package/server/data/bootstrap/shipping_shipments.json +0 -50
  277. package/server/data/bootstrap/support_faqs.json +0 -26
  278. package/server/data/bootstrap/users.json +0 -20
  279. package/server/data/bootstrap_loader.py +0 -15
  280. package/server/docker-entrypoint.sh +0 -56
  281. package/server/main.py +0 -3
  282. package/server/pyproject.toml +0 -36
  283. package/server/shared/__init__.py +0 -1
  284. package/server/shared/application/__init__.py +0 -3
  285. package/server/shared/application/health.py +0 -2
  286. package/server/shared/infrastructure/__init__.py +0 -10
  287. package/server/shared/infrastructure/runtime.py +0 -6
  288. package/server/shared/infrastructure/security.py +0 -33
  289. package/server/tests/e2e/test_domain_feature_flows.py +0 -483
  290. package/server/tests/test_health.py +0 -49
  291. package/server/uv.lock +0 -1169
@@ -1,483 +0,0 @@
1
- from collections.abc import Generator
2
-
3
- import pytest
4
- from fastapi.testclient import TestClient
5
-
6
- from api.http.app import app
7
- from contexts.auth.application import services as auth_services
8
- from contexts.auth.infrastructure import repository as auth_repository
9
- from contexts.alerts.infrastructure import repository as alerts_repository
10
- from contexts.catalog.application import services as catalog_services
11
- from contexts.catalog.infrastructure import repository as catalog_repository
12
- from contexts.fulfillment.infrastructure import repository as fulfillment_repository
13
- from contexts.inventory.application import services as inventory_services
14
- from contexts.inventory.infrastructure import repository as inventory_repository
15
- from contexts.orders.infrastructure import repository as orders_repository
16
- from contexts.shipping.infrastructure import repository as shipping_repository
17
- from contexts.support.infrastructure import repository as support_repository
18
- from contexts.user.infrastructure import factory as user_factory
19
-
20
-
21
- ADMIN_EMAIL = "admin@example.com"
22
- OPERATOR_EMAIL = "operator@example.com"
23
- PASSWORD = "<CHANGE_ME>"
24
-
25
-
26
- @pytest.fixture(autouse=True)
27
- def reset_in_memory_state() -> Generator[None, None, None]:
28
- auth_services._seed_auth_store.cache_clear()
29
- auth_repository.get_auth_repository.cache_clear()
30
- catalog_services._seed_catalog_store.cache_clear()
31
- catalog_repository.get_catalog_repository.cache_clear()
32
- inventory_services._seed_inventory_store.cache_clear()
33
- inventory_repository.get_inventory_repository.cache_clear()
34
- user_factory.get_user_repository.cache_clear()
35
- alerts_repository._alert_store = None
36
- orders_repository._order_store = None
37
- shipping_repository._shipment_store = None
38
- support_repository._support_faq_store = None
39
- fulfillment_repository._task_store = None
40
- fulfillment_repository._event_store = None
41
- fulfillment_repository._note_store = None
42
- yield
43
- auth_services._seed_auth_store.cache_clear()
44
- auth_repository.get_auth_repository.cache_clear()
45
- catalog_services._seed_catalog_store.cache_clear()
46
- catalog_repository.get_catalog_repository.cache_clear()
47
- inventory_services._seed_inventory_store.cache_clear()
48
- inventory_repository.get_inventory_repository.cache_clear()
49
- user_factory.get_user_repository.cache_clear()
50
- alerts_repository._alert_store = None
51
- orders_repository._order_store = None
52
- shipping_repository._shipment_store = None
53
- support_repository._support_faq_store = None
54
- fulfillment_repository._task_store = None
55
- fulfillment_repository._event_store = None
56
- fulfillment_repository._note_store = None
57
-
58
-
59
- def _login(client: TestClient, email: str = ADMIN_EMAIL) -> str:
60
- response = client.post(
61
- "/api/v1/auth/login",
62
- json={"email": email, "password": PASSWORD},
63
- )
64
- assert response.status_code == 200
65
- return response.json()["access_token"]
66
-
67
-
68
- def _auth_headers(token: str) -> dict[str, str]:
69
- return {"Authorization": f"Bearer {token}"}
70
-
71
-
72
- def test_aut_f001_login_and_me_issue_authenticated_identity() -> None:
73
- with TestClient(app) as client:
74
- token = _login(client)
75
- me_response = client.get("/api/v1/auth/me", headers=_auth_headers(token))
76
-
77
- assert me_response.status_code == 200
78
- assert me_response.json() == {
79
- "id": "user-1",
80
- "name": "Template Admin",
81
- "email": ADMIN_EMAIL,
82
- "role": "admin",
83
- "status": "active",
84
- }
85
-
86
-
87
- def test_usr_f001_admin_can_read_and_update_users_while_operator_is_forbidden() -> None:
88
- with TestClient(app) as client:
89
- admin_token = _login(client, ADMIN_EMAIL)
90
- operator_token = _login(client, OPERATOR_EMAIL)
91
-
92
- forbidden_response = client.get("/api/v1/users", headers=_auth_headers(operator_token))
93
- list_response = client.get("/api/v1/users", headers=_auth_headers(admin_token))
94
- detail_response = client.get("/api/v1/users/user-2", headers=_auth_headers(admin_token))
95
- patch_response = client.patch(
96
- "/api/v1/users/user-2/status",
97
- json={"status": "suspended"},
98
- headers=_auth_headers(admin_token),
99
- )
100
-
101
- assert forbidden_response.status_code == 403
102
- assert list_response.status_code == 200
103
- assert len(list_response.json()) == 2
104
- assert detail_response.status_code == 200
105
- assert detail_response.json()["email"] == OPERATOR_EMAIL
106
- assert patch_response.status_code == 200
107
- assert patch_response.json()["status"] == "suspended"
108
-
109
-
110
- def test_cat_f001_public_catalog_read_surfaces_return_live_product_data() -> None:
111
- with TestClient(app) as client:
112
- list_response = client.get("/api/v1/catalog/products")
113
- filtered_response = client.get("/api/v1/catalog/products", params={"status": "active"})
114
- detail_response = client.get("/api/v1/catalog/products/prd-1001")
115
-
116
- assert list_response.status_code == 200
117
- assert len(list_response.json()) == 3
118
- assert filtered_response.status_code == 200
119
- assert len(filtered_response.json()) == 1
120
- assert detail_response.status_code == 200
121
- assert detail_response.json()["slug"] == "trailshell-pack-jacket"
122
-
123
-
124
- def test_cat_f002_admin_can_create_update_and_archive_catalog_products() -> None:
125
- payload = {
126
- "slug": "metro-pack-vest",
127
- "name": "Metro Pack Vest",
128
- "brand": "Northstar Supply",
129
- "category": "outerwear",
130
- "status": "draft",
131
- "short_description": "Packable insulated vest for shoulder-season layering.",
132
- "description": "Metro Pack Vest blends light insulation with a compact stow pouch for travel days.",
133
- "hero_image": {
134
- "url": "https://images.templates.dev/catalog/metro-pack-vest/hero.jpg",
135
- "alt": "Metro Pack Vest in slate",
136
- },
137
- "gallery": [],
138
- "price": {"amount": 149000, "currency": "KRW"},
139
- "compare_at_price": None,
140
- "tags": ["new-drop"],
141
- "attributes": [{"name": "Fit", "value": "Regular"}],
142
- "variants": [
143
- {
144
- "sku": "MPV-SLT-M",
145
- "name": "Slate / M",
146
- "option_values": ["Slate", "M"],
147
- }
148
- ],
149
- }
150
-
151
- with TestClient(app) as client:
152
- admin_token = _login(client)
153
- create_response = client.post(
154
- "/api/v1/catalog/products",
155
- json=payload,
156
- headers=_auth_headers(admin_token),
157
- )
158
- created_product_id = create_response.json()["id"]
159
- update_response = client.put(
160
- f"/api/v1/catalog/products/{created_product_id}",
161
- json={
162
- "short_description": "Packable insulated vest for transit and weekend travel.",
163
- "tags": ["new-drop", "travel"],
164
- },
165
- headers=_auth_headers(admin_token),
166
- )
167
- status_response = client.patch(
168
- f"/api/v1/catalog/products/{created_product_id}/status",
169
- json={"status": "archived"},
170
- headers=_auth_headers(admin_token),
171
- )
172
-
173
- assert create_response.status_code == 201
174
- assert create_response.json()["slug"] == payload["slug"]
175
- assert update_response.status_code == 200
176
- assert update_response.json()["short_description"].startswith("Packable insulated vest")
177
- assert status_response.status_code == 200
178
- assert status_response.json()["status"] == "archived"
179
-
180
-
181
- def test_inv_f001_admin_can_read_and_mutate_inventory_levels() -> None:
182
- with TestClient(app) as client:
183
- admin_token = _login(client)
184
- operator_token = _login(client, OPERATOR_EMAIL)
185
-
186
- forbidden_response = client.get("/api/v1/inventory/levels", headers=_auth_headers(operator_token))
187
- list_response = client.get("/api/v1/inventory/levels", headers=_auth_headers(admin_token))
188
- detail_response = client.get(
189
- "/api/v1/inventory/levels/TSJ-BLK-S/fc-seoul",
190
- headers=_auth_headers(admin_token),
191
- )
192
- adjust_response = client.post(
193
- "/api/v1/inventory/levels/TSJ-BLK-S/fc-seoul/adjustments",
194
- json={"quantity_delta": 4, "reason": "cycle_count"},
195
- headers=_auth_headers(admin_token),
196
- )
197
- reserve_response = client.post(
198
- "/api/v1/inventory/levels/TSJ-BLK-S/fc-seoul/reservations",
199
- json={"quantity": 2, "reference_id": "ORD-NEW-1"},
200
- headers=_auth_headers(admin_token),
201
- )
202
- release_response = client.post(
203
- "/api/v1/inventory/levels/TSJ-BLK-S/fc-seoul/releases",
204
- json={"quantity": 1, "reference_id": "ORD-NEW-1"},
205
- headers=_auth_headers(admin_token),
206
- )
207
- set_response = client.put(
208
- "/api/v1/inventory/levels/TSJ-BLK-S/fc-seoul",
209
- json={
210
- "on_hand": 20,
211
- "reserved": 3,
212
- "safety_stock": 4,
213
- "reorder_point": 8,
214
- },
215
- headers=_auth_headers(admin_token),
216
- )
217
-
218
- assert forbidden_response.status_code == 403
219
- assert list_response.status_code == 200
220
- assert len(list_response.json()) == 6
221
- assert detail_response.status_code == 200
222
- assert detail_response.json()["available_to_sell"] == 15
223
- assert adjust_response.status_code == 200
224
- assert adjust_response.json()["action"] == "adjusted"
225
- assert adjust_response.json()["level"]["on_hand"] == 22
226
- assert reserve_response.status_code == 200
227
- assert reserve_response.json()["level"]["reserved"] == 5
228
- assert release_response.status_code == 200
229
- assert release_response.json()["level"]["reserved"] == 4
230
- assert set_response.status_code == 200
231
- assert set_response.json()["level"]["available_to_sell"] == 17
232
-
233
-
234
- def test_ord_f001_authenticated_users_can_read_create_and_update_orders() -> None:
235
- with TestClient(app) as client:
236
- operator_token = _login(client, OPERATOR_EMAIL)
237
-
238
- unauthorized_response = client.get("/api/v1/orders")
239
- overview_response = client.get("/api/v1/orders/overview", headers=_auth_headers(operator_token))
240
- list_response = client.get("/api/v1/orders", headers=_auth_headers(operator_token))
241
- create_response = client.post(
242
- "/api/v1/orders",
243
- json={
244
- "product_id": "prd-1002",
245
- "product_name": "Commuter Utility Sling",
246
- "customer_name": "Daegu Concept Store",
247
- "seller_name": "Harbor Line",
248
- "amount_krw": 129000,
249
- "stage": "결제 검증",
250
- "status": "Pending",
251
- "fulfillment_status": "Queued",
252
- "sla": "35 min",
253
- },
254
- headers=_auth_headers(operator_token),
255
- )
256
- created_order_id = create_response.json()["id"]
257
- status_response = client.patch(
258
- f"/api/v1/orders/{created_order_id}/status",
259
- json={
260
- "status": "Paid",
261
- "fulfillment_status": "Packing",
262
- "stage": "패킹 준비",
263
- },
264
- headers=_auth_headers(operator_token),
265
- )
266
-
267
- assert unauthorized_response.status_code == 401
268
- assert overview_response.status_code == 200
269
- assert overview_response.json()["selected_order"]["product_name"]
270
- assert list_response.status_code == 200
271
- assert len(list_response.json()) == 4
272
- assert create_response.status_code == 201
273
- assert create_response.json()["customer_name"] == "Daegu Concept Store"
274
- assert status_response.status_code == 200
275
- assert status_response.json()["status"] == "Paid"
276
- assert status_response.json()["fulfillment_status"] == "Packing"
277
-
278
-
279
- def test_ord_f002_admin_only_order_surfaces_require_admin_role() -> None:
280
- with TestClient(app) as client:
281
- admin_token = _login(client, ADMIN_EMAIL)
282
- operator_token = _login(client, OPERATOR_EMAIL)
283
-
284
- forbidden_response = client.get(
285
- "/api/v1/orders/admin/overview",
286
- headers=_auth_headers(operator_token),
287
- )
288
- overview_response = client.get(
289
- "/api/v1/orders/admin/overview",
290
- headers=_auth_headers(admin_token),
291
- )
292
- queue_response = client.get(
293
- "/api/v1/orders/admin/queue",
294
- headers=_auth_headers(admin_token),
295
- )
296
-
297
- assert forbidden_response.status_code == 403
298
- assert overview_response.status_code == 200
299
- assert len(overview_response.json()["cards"]) == 3
300
- assert "alerts" not in overview_response.json()
301
- assert queue_response.status_code == 200
302
- assert queue_response.json()[0]["product_name"]
303
-
304
-
305
- def test_sup_f001_admin_can_read_create_and_hide_support_faqs() -> None:
306
- with TestClient(app) as client:
307
- admin_token = _login(client, ADMIN_EMAIL)
308
- operator_token = _login(client, OPERATOR_EMAIL)
309
-
310
- forbidden_response = client.get(
311
- "/api/v1/support/faqs",
312
- headers=_auth_headers(operator_token),
313
- )
314
- list_response = client.get(
315
- "/api/v1/support/faqs",
316
- headers=_auth_headers(admin_token),
317
- )
318
- create_response = client.post(
319
- "/api/v1/support/faqs",
320
- json={
321
- "question": "교환 요청은 어디서 확인하나요?",
322
- "answer": "교환 요청은 운영 지원 큐와 주문 상세에서 함께 확인합니다.",
323
- "category": "교환",
324
- "visibility": "노출",
325
- },
326
- headers=_auth_headers(admin_token),
327
- )
328
- created_faq_id = create_response.json()["id"]
329
- visibility_response = client.patch(
330
- f"/api/v1/support/faqs/{created_faq_id}/visibility",
331
- json={"visibility": "숨김"},
332
- headers=_auth_headers(admin_token),
333
- )
334
-
335
- assert forbidden_response.status_code == 403
336
- assert list_response.status_code == 200
337
- assert len(list_response.json()) == 3
338
- assert create_response.status_code == 201
339
- assert create_response.json()["category"] == "교환"
340
- assert visibility_response.status_code == 200
341
- assert visibility_response.json()["visibility"] == "숨김"
342
-
343
-
344
- def test_ful_f001_authenticated_users_can_read_and_transition_fulfillment_tasks() -> None:
345
- with TestClient(app) as client:
346
- operator_token = _login(client, OPERATOR_EMAIL)
347
-
348
- unauthorized_response = client.get("/api/v1/fulfillment/overview")
349
- overview_response = client.get(
350
- "/api/v1/fulfillment/overview",
351
- headers=_auth_headers(operator_token),
352
- )
353
- board_response = client.get(
354
- "/api/v1/fulfillment/board",
355
- headers=_auth_headers(operator_token),
356
- )
357
- transition_response = client.patch(
358
- "/api/v1/fulfillment/tasks/ft-1/status",
359
- json={"status": "In progress", "stage": "Packing"},
360
- headers=_auth_headers(operator_token),
361
- )
362
-
363
- assert unauthorized_response.status_code == 401
364
- assert overview_response.status_code == 200
365
- assert overview_response.json()["throughput_total"] == "5"
366
- assert board_response.status_code == 200
367
- assert len(board_response.json()["tasks"]) == 4
368
- assert len(board_response.json()["notes"]) == 3
369
- assert transition_response.status_code == 200
370
- assert transition_response.json()["previous_status"] == "Queued"
371
- assert transition_response.json()["status"] == "In progress"
372
-
373
-
374
- def test_shp_f001_authenticated_users_can_read_shipping_overview() -> None:
375
- with TestClient(app) as client:
376
- operator_token = _login(client, OPERATOR_EMAIL)
377
-
378
- unauthorized_response = client.get("/api/v1/shipping/overview")
379
- overview_response = client.get(
380
- "/api/v1/shipping/overview",
381
- headers=_auth_headers(operator_token),
382
- )
383
-
384
- assert unauthorized_response.status_code == 401
385
- assert overview_response.status_code == 200
386
- assert len(overview_response.json()["stats"]) == 3
387
- assert len(overview_response.json()["carriers"]) == 3
388
- assert overview_response.json()["highlighted_route"]
389
-
390
-
391
- def test_shp_f002_authenticated_users_can_read_shipping_shipments() -> None:
392
- with TestClient(app) as client:
393
- operator_token = _login(client, OPERATOR_EMAIL)
394
- list_response = client.get(
395
- "/api/v1/shipping/shipments",
396
- headers=_auth_headers(operator_token),
397
- )
398
-
399
- assert list_response.status_code == 200
400
- assert len(list_response.json()) == 4
401
- assert list_response.json()[0]["shipment_id"] == "shp-1001"
402
- assert list_response.json()[0]["tracking_number"]
403
-
404
-
405
- def test_shp_f003_authenticated_users_can_transition_shipping_shipments() -> None:
406
- with TestClient(app) as client:
407
- operator_token = _login(client, OPERATOR_EMAIL)
408
- transition_response = client.patch(
409
- "/api/v1/shipping/shipments/shp-1001/status",
410
- json={"status": "Delivered", "last_event": "고객 인수 완료"},
411
- headers=_auth_headers(operator_token),
412
- )
413
- list_response = client.get(
414
- "/api/v1/shipping/shipments",
415
- headers=_auth_headers(operator_token),
416
- )
417
-
418
- assert transition_response.status_code == 200
419
- assert transition_response.json()["shipment_id"] == "shp-1001"
420
- assert transition_response.json()["previous_status"] == "In transit"
421
- assert transition_response.json()["status"] == "Delivered"
422
- assert transition_response.json()["last_event"] == "고객 인수 완료"
423
- assert list_response.status_code == 200
424
- assert list_response.json()[0]["status"] == "Delivered"
425
-
426
-
427
- def test_alr_f001_admin_only_alert_feed_requires_admin_role() -> None:
428
- with TestClient(app) as client:
429
- admin_token = _login(client, ADMIN_EMAIL)
430
- operator_token = _login(client, OPERATOR_EMAIL)
431
-
432
- forbidden_response = client.get(
433
- "/api/v1/alerts",
434
- headers=_auth_headers(operator_token),
435
- )
436
- list_response = client.get(
437
- "/api/v1/alerts",
438
- headers=_auth_headers(admin_token),
439
- )
440
-
441
- assert forbidden_response.status_code == 403
442
- assert list_response.status_code == 200
443
- assert list_response.json()["unread_count"] == 3
444
- assert len(list_response.json()["items"]) == 4
445
-
446
-
447
- def test_alr_f002_admin_can_mark_single_alert_as_read() -> None:
448
- with TestClient(app) as client:
449
- admin_token = _login(client, ADMIN_EMAIL)
450
- read_response = client.post(
451
- "/api/v1/alerts/alr-1001/read",
452
- headers=_auth_headers(admin_token),
453
- )
454
- list_response = client.get(
455
- "/api/v1/alerts",
456
- headers=_auth_headers(admin_token),
457
- )
458
-
459
- assert read_response.status_code == 200
460
- assert read_response.json()["id"] == "alr-1001"
461
- assert read_response.json()["read"] is True
462
-
463
- assert list_response.status_code == 200
464
- assert list_response.json()["unread_count"] == 2
465
-
466
-
467
- def test_alr_f003_admin_can_mark_all_alerts_as_read() -> None:
468
- with TestClient(app) as client:
469
- admin_token = _login(client, ADMIN_EMAIL)
470
- read_all_response = client.post(
471
- "/api/v1/alerts/read-all",
472
- headers=_auth_headers(admin_token),
473
- )
474
- list_response = client.get(
475
- "/api/v1/alerts",
476
- headers=_auth_headers(admin_token),
477
- )
478
-
479
- assert read_all_response.status_code == 200
480
- assert read_all_response.json()["updated_count"] == 3
481
- assert read_all_response.json()["unread_count"] == 0
482
- assert list_response.status_code == 200
483
- assert list_response.json()["unread_count"] == 0
@@ -1,49 +0,0 @@
1
- from fastapi.testclient import TestClient
2
-
3
- from api.http.app import app
4
-
5
-
6
- def test_health() -> None:
7
- with TestClient(app) as client:
8
- response = client.get("/health")
9
- assert response.status_code == 200
10
- assert response.json() == {"status": "ok"}
11
-
12
-
13
- def test_status() -> None:
14
- with TestClient(app) as client:
15
- response = client.get("/api/v1/status")
16
- assert response.status_code == 200
17
- assert response.json()["status"] == "healthy"
18
-
19
-
20
- def test_login_and_me() -> None:
21
- with TestClient(app) as client:
22
- login_response = client.post(
23
- "/api/v1/auth/login",
24
- json={"email": "admin@example.com", "password": "<CHANGE_ME>"},
25
- )
26
- assert login_response.status_code == 200
27
- token = login_response.json()["access_token"]
28
-
29
- me_response = client.get("/api/v1/auth/me", headers={"Authorization": f"Bearer {token}"})
30
- assert me_response.status_code == 200
31
- assert me_response.json()["email"] == "admin@example.com"
32
-
33
-
34
- def test_users_context_examples() -> None:
35
- with TestClient(app) as client:
36
- login_response = client.post(
37
- "/api/v1/auth/login",
38
- json={"email": "admin@example.com", "password": "<CHANGE_ME>"},
39
- )
40
- assert login_response.status_code == 200
41
- headers = {"Authorization": f"Bearer {login_response.json()['access_token']}"}
42
-
43
- users_response = client.get("/api/v1/users", headers=headers)
44
- assert users_response.status_code == 200
45
- assert len(users_response.json()) == 2
46
-
47
- user_response = client.get("/api/v1/users/user-1", headers=headers)
48
- assert user_response.status_code == 200
49
- assert user_response.json()["timezone"] == "Asia/Seoul"