@cachly-dev/mcp-server 0.10.65 → 0.10.67

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 (277) hide show
  1. package/dist/src/embeddings.js +10 -0
  2. package/dist/src/embeddings.js.map +1 -1
  3. package/dist/src/handlers/brain.js +2 -1
  4. package/dist/src/handlers/brain.js.map +1 -1
  5. package/dist/src/handlers/cache.js +4 -0
  6. package/dist/src/handlers/cache.js.map +1 -1
  7. package/dist/src/handlers/tco.js +1 -0
  8. package/dist/src/handlers/tco.js.map +1 -1
  9. package/dist/src/index.js +2 -2
  10. package/dist/src/index.js.map +1 -1
  11. package/package.json +7 -1
  12. package/server.json +2 -2
  13. package/.env.test.example +0 -23
  14. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -50
  15. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  16. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -25
  17. package/.github/workflows/eas-build-android.yml +0 -58
  18. package/.github/workflows/eas-update.yml +0 -55
  19. package/.github/workflows/publish.yml +0 -72
  20. package/BENCH.md +0 -113
  21. package/CHANGELOG.md +0 -653
  22. package/Dockerfile +0 -16
  23. package/LAUNCH_TEXTS.md +0 -182
  24. package/PROGRESS.md +0 -141
  25. package/STRATEGY.md +0 -228
  26. package/VISION_10X.md +0 -133
  27. package/apps/travel-chaos-organizer/.env.example +0 -49
  28. package/apps/travel-chaos-organizer/DEPLOY.md +0 -328
  29. package/apps/travel-chaos-organizer/Makefile +0 -57
  30. package/apps/travel-chaos-organizer/README.md +0 -159
  31. package/apps/travel-chaos-organizer/backend/.env.example +0 -10
  32. package/apps/travel-chaos-organizer/backend/Dockerfile +0 -25
  33. package/apps/travel-chaos-organizer/backend/alembic/env.py +0 -56
  34. package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +0 -24
  35. package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
  36. package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +0 -24
  37. package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +0 -24
  38. package/apps/travel-chaos-organizer/backend/alembic.ini +0 -39
  39. package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
  40. package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
  41. package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +0 -81
  42. package/apps/travel-chaos-organizer/backend/app/config.py +0 -66
  43. package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
  44. package/apps/travel-chaos-organizer/backend/app/db/database.py +0 -20
  45. package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +0 -71
  46. package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +0 -31
  47. package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +0 -4
  48. package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +0 -5
  49. package/apps/travel-chaos-organizer/backend/app/limiter.py +0 -4
  50. package/apps/travel-chaos-organizer/backend/app/logging_config.py +0 -25
  51. package/apps/travel-chaos-organizer/backend/app/main.py +0 -77
  52. package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
  53. package/apps/travel-chaos-organizer/backend/app/models/schemas.py +0 -142
  54. package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
  55. package/apps/travel-chaos-organizer/backend/app/routers/admin.py +0 -237
  56. package/apps/travel-chaos-organizer/backend/app/routers/cache.py +0 -13
  57. package/apps/travel-chaos-organizer/backend/app/routers/events.py +0 -29
  58. package/apps/travel-chaos-organizer/backend/app/routers/export.py +0 -122
  59. package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +0 -122
  60. package/apps/travel-chaos-organizer/backend/app/routers/items.py +0 -130
  61. package/apps/travel-chaos-organizer/backend/app/routers/mail.py +0 -108
  62. package/apps/travel-chaos-organizer/backend/app/routers/parse.py +0 -303
  63. package/apps/travel-chaos-organizer/backend/app/routers/payments.py +0 -179
  64. package/apps/travel-chaos-organizer/backend/app/routers/trips.py +0 -134
  65. package/apps/travel-chaos-organizer/backend/app/routers/users.py +0 -169
  66. package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +0 -37
  67. package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
  68. package/apps/travel-chaos-organizer/backend/app/services/cache.py +0 -131
  69. package/apps/travel-chaos-organizer/backend/app/services/drip.py +0 -128
  70. package/apps/travel-chaos-organizer/backend/app/services/email.py +0 -61
  71. package/apps/travel-chaos-organizer/backend/app/services/errors.py +0 -44
  72. package/apps/travel-chaos-organizer/backend/app/services/notifier.py +0 -10
  73. package/apps/travel-chaos-organizer/backend/app/services/ollama.py +0 -101
  74. package/apps/travel-chaos-organizer/backend/app/services/parser.py +0 -22
  75. package/apps/travel-chaos-organizer/backend/app/services/quota.py +0 -85
  76. package/apps/travel-chaos-organizer/backend/app/services/sentry.py +0 -11
  77. package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +0 -35
  78. package/apps/travel-chaos-organizer/backend/pytest.ini +0 -4
  79. package/apps/travel-chaos-organizer/backend/requirements-dev.txt +0 -6
  80. package/apps/travel-chaos-organizer/backend/requirements.txt +0 -25
  81. package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
  82. package/apps/travel-chaos-organizer/backend/tests/conftest.py +0 -223
  83. package/apps/travel-chaos-organizer/backend/tests/test_admin.py +0 -99
  84. package/apps/travel-chaos-organizer/backend/tests/test_cache.py +0 -142
  85. package/apps/travel-chaos-organizer/backend/tests/test_drip.py +0 -32
  86. package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +0 -121
  87. package/apps/travel-chaos-organizer/backend/tests/test_export.py +0 -48
  88. package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +0 -107
  89. package/apps/travel-chaos-organizer/backend/tests/test_items.py +0 -138
  90. package/apps/travel-chaos-organizer/backend/tests/test_mail.py +0 -113
  91. package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +0 -145
  92. package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +0 -98
  93. package/apps/travel-chaos-organizer/backend/tests/test_parser.py +0 -72
  94. package/apps/travel-chaos-organizer/backend/tests/test_payments.py +0 -163
  95. package/apps/travel-chaos-organizer/backend/tests/test_quota.py +0 -46
  96. package/apps/travel-chaos-organizer/backend/tests/test_search.py +0 -67
  97. package/apps/travel-chaos-organizer/backend/tests/test_trips.py +0 -123
  98. package/apps/travel-chaos-organizer/backend/tests/test_users.py +0 -52
  99. package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
  100. package/apps/travel-chaos-organizer/caddy/Caddyfile +0 -27
  101. package/apps/travel-chaos-organizer/docker-compose.node1.yml +0 -31
  102. package/apps/travel-chaos-organizer/docker-compose.override.yml +0 -14
  103. package/apps/travel-chaos-organizer/docker-compose.yml +0 -128
  104. package/apps/travel-chaos-organizer/frontend/.env.example +0 -13
  105. package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +0 -80
  106. package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +0 -166
  107. package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +0 -130
  108. package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +0 -381
  109. package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +0 -346
  110. package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +0 -363
  111. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +0 -34
  112. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +0 -35
  113. package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +0 -91
  114. package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +0 -71
  115. package/apps/travel-chaos-organizer/frontend/app/auth.tsx +0 -29
  116. package/apps/travel-chaos-organizer/frontend/app/index.tsx +0 -16
  117. package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +0 -119
  118. package/apps/travel-chaos-organizer/frontend/app.config.ts +0 -54
  119. package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +0 -66
  120. package/apps/travel-chaos-organizer/frontend/assets/icon.svg +0 -79
  121. package/apps/travel-chaos-organizer/frontend/assets/splash.svg +0 -15
  122. package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +0 -102
  123. package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +0 -107
  124. package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +0 -109
  125. package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +0 -45
  126. package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +0 -19
  127. package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +0 -155
  128. package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +0 -55
  129. package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +0 -80
  130. package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +0 -39
  131. package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +0 -39
  132. package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +0 -70
  133. package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +0 -121
  134. package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +0 -70
  135. package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +0 -73
  136. package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +0 -130
  137. package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +0 -60
  138. package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +0 -42
  139. package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +0 -146
  140. package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +0 -70
  141. package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +0 -44
  142. package/apps/travel-chaos-organizer/frontend/eas.json +0 -22
  143. package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +0 -24
  144. package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +0 -60
  145. package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +0 -112
  146. package/apps/travel-chaos-organizer/frontend/lib/api.ts +0 -223
  147. package/apps/travel-chaos-organizer/frontend/lib/auth.ts +0 -93
  148. package/apps/travel-chaos-organizer/frontend/lib/db.ts +0 -86
  149. package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +0 -54
  150. package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +0 -13
  151. package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +0 -112
  152. package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +0 -145
  153. package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +0 -43
  154. package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +0 -28
  155. package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +0 -44
  156. package/apps/travel-chaos-organizer/frontend/package-lock.json +0 -13221
  157. package/apps/travel-chaos-organizer/frontend/package.json +0 -44
  158. package/apps/travel-chaos-organizer/frontend/tsconfig.json +0 -23
  159. package/apps/travel-chaos-organizer/keycloak/realm-export.json +0 -59
  160. package/apps/travel-chaos-organizer/landing/index.html +0 -773
  161. package/apps/travel-chaos-organizer/scripts/backup.sh +0 -14
  162. package/apps/travel-chaos-organizer/scripts/configure-env.sh +0 -82
  163. package/apps/travel-chaos-organizer/scripts/deploy.sh +0 -54
  164. package/apps/travel-chaos-organizer/scripts/health-check.sh +0 -36
  165. package/apps/travel-chaos-organizer/scripts/migrate.sh +0 -7
  166. package/apps/travel-chaos-organizer/scripts/server-setup.sh +0 -85
  167. package/apps/travel-chaos-organizer/scripts/smoke-test.sh +0 -29
  168. package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +0 -71
  169. package/apps/travel-chaos-organizer/telegram-bot/.env.example +0 -3
  170. package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +0 -6
  171. package/apps/travel-chaos-organizer/telegram-bot/bot.py +0 -504
  172. package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +0 -3
  173. package/dist/src/__tests__/auth.test.d.ts +0 -1
  174. package/dist/src/__tests__/auth.test.js +0 -107
  175. package/dist/src/__tests__/auth.test.js.map +0 -1
  176. package/dist/src/__tests__/brain-flow.test.d.ts +0 -12
  177. package/dist/src/__tests__/brain-flow.test.js +0 -1241
  178. package/dist/src/__tests__/brain-flow.test.js.map +0 -1
  179. package/dist/src/__tests__/confidence.test.d.ts +0 -13
  180. package/dist/src/__tests__/confidence.test.js +0 -233
  181. package/dist/src/__tests__/confidence.test.js.map +0 -1
  182. package/dist/src/__tests__/e2e/api-auth.test.d.ts +0 -7
  183. package/dist/src/__tests__/e2e/api-auth.test.js +0 -162
  184. package/dist/src/__tests__/e2e/api-auth.test.js.map +0 -1
  185. package/dist/src/__tests__/e2e/api-health.test.d.ts +0 -7
  186. package/dist/src/__tests__/e2e/api-health.test.js +0 -95
  187. package/dist/src/__tests__/e2e/api-health.test.js.map +0 -1
  188. package/dist/src/__tests__/e2e/cli.test.d.ts +0 -10
  189. package/dist/src/__tests__/e2e/cli.test.js +0 -204
  190. package/dist/src/__tests__/e2e/cli.test.js.map +0 -1
  191. package/dist/src/__tests__/e2e/config.d.ts +0 -35
  192. package/dist/src/__tests__/e2e/config.js +0 -53
  193. package/dist/src/__tests__/e2e/config.js.map +0 -1
  194. package/dist/src/__tests__/e2e/funnel.test.d.ts +0 -13
  195. package/dist/src/__tests__/e2e/funnel.test.js +0 -90
  196. package/dist/src/__tests__/e2e/funnel.test.js.map +0 -1
  197. package/dist/src/__tests__/e2e/mcp-tools.test.d.ts +0 -7
  198. package/dist/src/__tests__/e2e/mcp-tools.test.js +0 -129
  199. package/dist/src/__tests__/e2e/mcp-tools.test.js.map +0 -1
  200. package/dist/src/__tests__/e2e/provisioning.test.d.ts +0 -15
  201. package/dist/src/__tests__/e2e/provisioning.test.js +0 -123
  202. package/dist/src/__tests__/e2e/provisioning.test.js.map +0 -1
  203. package/dist/src/__tests__/handlers-unit.test.d.ts +0 -12
  204. package/dist/src/__tests__/handlers-unit.test.js +0 -505
  205. package/dist/src/__tests__/handlers-unit.test.js.map +0 -1
  206. package/dist/src/__tests__/namespace.test.d.ts +0 -1
  207. package/dist/src/__tests__/namespace.test.js +0 -114
  208. package/dist/src/__tests__/namespace.test.js.map +0 -1
  209. package/dist/src/__tests__/rerank.test.d.ts +0 -1
  210. package/dist/src/__tests__/rerank.test.js +0 -125
  211. package/dist/src/__tests__/rerank.test.js.map +0 -1
  212. package/dist/src/__tests__/utils.test.d.ts +0 -1
  213. package/dist/src/__tests__/utils.test.js +0 -83
  214. package/dist/src/__tests__/utils.test.js.map +0 -1
  215. package/dist/src/bench/cachly-bench.d.ts +0 -39
  216. package/dist/src/bench/cachly-bench.js +0 -223
  217. package/dist/src/bench/cachly-bench.js.map +0 -1
  218. package/dist/src/bench/fixtures.d.ts +0 -37
  219. package/dist/src/bench/fixtures.js +0 -225
  220. package/dist/src/bench/fixtures.js.map +0 -1
  221. package/dist/src/search.test.d.ts +0 -1
  222. package/dist/src/search.test.js +0 -713
  223. package/dist/src/search.test.js.map +0 -1
  224. package/eslint.config.js +0 -16
  225. package/glama.json +0 -36
  226. package/llms.txt +0 -155
  227. package/packages/telegram-notify/SPLIT_PLAN.md +0 -106
  228. package/packages/telegram-notify/client.ts +0 -88
  229. package/packages/telegram-notify/emojis.json +0 -22
  230. package/packages/telegram-notify/pyproject.toml +0 -17
  231. package/packages/telegram-notify/telegram_notify/__init__.py +0 -3
  232. package/packages/telegram-notify/telegram_notify/client.py +0 -87
  233. package/packages/telegram-notify/telegram_notify/emojis.json +0 -22
  234. package/scripts/e2e-run.sh +0 -160
  235. package/scripts/generate-icons.js +0 -28
  236. package/smithery.yaml +0 -45
  237. package/src/__tests__/auth.test.ts +0 -147
  238. package/src/__tests__/brain-flow.test.ts +0 -1472
  239. package/src/__tests__/confidence.test.ts +0 -285
  240. package/src/__tests__/e2e/api-auth.test.ts +0 -182
  241. package/src/__tests__/e2e/api-health.test.ts +0 -103
  242. package/src/__tests__/e2e/cli.test.ts +0 -228
  243. package/src/__tests__/e2e/config.ts +0 -53
  244. package/src/__tests__/e2e/funnel.test.ts +0 -99
  245. package/src/__tests__/e2e/mcp-tools.test.ts +0 -191
  246. package/src/__tests__/e2e/provisioning.test.ts +0 -128
  247. package/src/__tests__/handlers-unit.test.ts +0 -677
  248. package/src/__tests__/namespace.test.ts +0 -126
  249. package/src/__tests__/rerank.test.ts +0 -141
  250. package/src/__tests__/utils.test.ts +0 -81
  251. package/src/auth.ts +0 -46
  252. package/src/bench/cachly-bench.ts +0 -263
  253. package/src/bench/fixtures.ts +0 -256
  254. package/src/ckg.ts +0 -103
  255. package/src/confidence.ts +0 -38
  256. package/src/embeddings.ts +0 -217
  257. package/src/handlers/advanced.ts +0 -647
  258. package/src/handlers/brain.ts +0 -2622
  259. package/src/handlers/cache.ts +0 -633
  260. package/src/handlers/context.ts +0 -222
  261. package/src/handlers/fedbrain.ts +0 -1232
  262. package/src/handlers/instances.ts +0 -180
  263. package/src/handlers/roadmap.ts +0 -201
  264. package/src/handlers/syndicate.ts +0 -460
  265. package/src/handlers/tco.ts +0 -345
  266. package/src/handlers/team.ts +0 -1249
  267. package/src/index.ts +0 -2815
  268. package/src/namespace.ts +0 -128
  269. package/src/notifier.ts +0 -5
  270. package/src/rerank.ts +0 -148
  271. package/src/search.test.ts +0 -848
  272. package/src/search.ts +0 -1781
  273. package/src/tools.ts +0 -2056
  274. package/src/utils.ts +0 -90
  275. package/test-features.cjs +0 -23
  276. package/tsconfig.json +0 -16
  277. package/vitest.config.ts +0 -14
@@ -1,107 +0,0 @@
1
- """
2
- Tests for /api/v1/inbox
3
-
4
- Covers:
5
- list_inbox – empty, pending filter, other status filters
6
- assign – moves item to trip, updates status to 'assigned', migrates attachments
7
- reject – sets status to 'rejected', idempotent on 404
8
-
9
- Run: pytest tests/test_inbox.py -v
10
- """
11
-
12
- import pytest
13
- from httpx import AsyncClient
14
- from tests.conftest import make_trip
15
-
16
- pytestmark = pytest.mark.asyncio
17
-
18
-
19
- async def _create_trip(client: AsyncClient) -> str:
20
- r = await client.post("/api/v1/trips", json=make_trip())
21
- assert r.status_code == 201
22
- return r.json()["id"]
23
-
24
-
25
- async def _seed_inbox(client: AsyncClient) -> str:
26
- """Parse some text so it lands in inbox (no trip_id → inbox)."""
27
- r = await client.post(
28
- "/api/v1/parse/text",
29
- data={"raw_text": "Flight LH 400 Frankfurt to New York on Aug 15"},
30
- )
31
- assert r.status_code == 200
32
- # fetch inbox to get the id
33
- inbox = await client.get("/api/v1/inbox")
34
- assert inbox.status_code == 200
35
- items = inbox.json()
36
- assert len(items) > 0
37
- return items[0]["id"]
38
-
39
-
40
- # ── list ──────────────────────────────────────────────────────────────────────
41
-
42
- async def test_inbox_empty(client):
43
- r = await client.get("/api/v1/inbox")
44
- assert r.status_code == 200
45
- assert r.json() == []
46
-
47
-
48
- async def test_inbox_default_status_is_pending(client, mock_ollama_flight):
49
- await _seed_inbox(client)
50
- r = await client.get("/api/v1/inbox")
51
- assert r.status_code == 200
52
- items = r.json()
53
- assert all(i["status"] == "pending" for i in items)
54
-
55
-
56
- async def test_inbox_filter_by_status(client, mock_ollama_flight):
57
- inbox_id = await _seed_inbox(client)
58
- await client.delete(f"/api/v1/inbox/{inbox_id}")
59
-
60
- r = await client.get("/api/v1/inbox?status_filter=rejected")
61
- assert r.status_code == 200
62
- assert any(i["id"] == inbox_id for i in r.json())
63
-
64
-
65
- # ── assign ────────────────────────────────────────────────────────────────────
66
-
67
- async def test_assign_creates_trip_item(client, mock_ollama_flight):
68
- trip_id = await _create_trip(client)
69
- inbox_id = await _seed_inbox(client)
70
-
71
- r = await client.post(
72
- f"/api/v1/inbox/{inbox_id}/assign",
73
- json={"trip_id": trip_id, "type": "flight"},
74
- )
75
- assert r.status_code == 200
76
- assert "trip_item_id" in r.json()
77
-
78
-
79
- async def test_assign_marks_inbox_as_assigned(client, mock_ollama_flight):
80
- trip_id = await _create_trip(client)
81
- inbox_id = await _seed_inbox(client)
82
- await client.post(f"/api/v1/inbox/{inbox_id}/assign",
83
- json={"trip_id": trip_id, "type": "flight"})
84
-
85
- r = await client.get("/api/v1/inbox?status_filter=assigned")
86
- assert any(i["id"] == inbox_id for i in r.json())
87
-
88
-
89
- async def test_assign_not_found(client):
90
- r = await client.post(
91
- "/api/v1/inbox/00000000-0000-0000-0000-000000000000/assign",
92
- json={"trip_id": "00000000-0000-0000-0000-000000000001", "type": "other"},
93
- )
94
- assert r.status_code == 404
95
-
96
-
97
- # ── reject ────────────────────────────────────────────────────────────────────
98
-
99
- async def test_reject_inbox_item(client, mock_ollama_flight):
100
- inbox_id = await _seed_inbox(client)
101
- r = await client.delete(f"/api/v1/inbox/{inbox_id}")
102
- assert r.status_code == 204
103
-
104
-
105
- async def test_reject_not_found(client):
106
- r = await client.delete("/api/v1/inbox/00000000-0000-0000-0000-000000000000")
107
- assert r.status_code == 404
@@ -1,138 +0,0 @@
1
- """
2
- Tests for /api/v1/trips/{trip_id}/items
3
-
4
- Covers:
5
- list_items – empty, sorted by event_at, trip isolation
6
- create_item – happy path, optional fields, missing title → 422
7
- update_item – partial patch, type change, 404 for wrong trip
8
- delete_item – removes item, 404 guard
9
- timeline order – items sorted chronologically regardless of insert order
10
-
11
- Run: pytest tests/test_items.py -v
12
- """
13
- import pytest
14
- from httpx import AsyncClient
15
- from tests.conftest import make_trip, make_item
16
-
17
- pytestmark = pytest.mark.asyncio
18
-
19
-
20
- async def _create_trip(client: AsyncClient) -> str:
21
- r = await client.post("/api/v1/trips", json=make_trip())
22
- assert r.status_code == 201
23
- return r.json()["id"]
24
-
25
-
26
- async def _create_item(client: AsyncClient, trip_id: str, **kwargs) -> dict:
27
- r = await client.post(f"/api/v1/trips/{trip_id}/items", json=make_item(**kwargs))
28
- assert r.status_code == 201, r.text
29
- return r.json()
30
-
31
-
32
- # ── list ──────────────────────────────────────────────────────────────────────
33
-
34
- async def test_list_items_empty(client):
35
- trip_id = await _create_trip(client)
36
- r = await client.get(f"/api/v1/trips/{trip_id}/items")
37
- assert r.status_code == 200
38
- assert r.json() == []
39
-
40
-
41
- async def test_list_items_wrong_trip_404(client):
42
- r = await client.get("/api/v1/trips/00000000-0000-0000-0000-000000000000/items")
43
- assert r.status_code == 404
44
-
45
-
46
- async def test_timeline_sorted_by_event_at(client):
47
- trip_id = await _create_trip(client)
48
- await _create_item(client, trip_id, title="Late flight", event_at="2024-08-20T18:00:00")
49
- await _create_item(client, trip_id, title="Early flight", event_at="2024-08-15T06:00:00")
50
- await _create_item(client, trip_id, title="Mid hotel", event_at="2024-08-17T14:00:00")
51
-
52
- r = await client.get(f"/api/v1/trips/{trip_id}/items")
53
- titles = [i["title"] for i in r.json()]
54
- assert titles == ["Early flight", "Mid hotel", "Late flight"]
55
-
56
-
57
- # ── create ────────────────────────────────────────────────────────────────────
58
-
59
- async def test_create_item_happy_path(client):
60
- trip_id = await _create_trip(client)
61
- r = await client.post(f"/api/v1/trips/{trip_id}/items", json=make_item(title="LH 400 FRA→JFK"))
62
- assert r.status_code == 201
63
- body = r.json()
64
- assert body["title"] == "LH 400 FRA→JFK"
65
- assert body["type"] == "flight"
66
- assert body["trip_id"] == trip_id
67
-
68
-
69
- async def test_create_item_missing_title_rejected(client):
70
- trip_id = await _create_trip(client)
71
- r = await client.post(f"/api/v1/trips/{trip_id}/items", json={"type": "flight"})
72
- assert r.status_code == 422
73
-
74
-
75
- async def test_create_item_optional_fields_nullable(client):
76
- trip_id = await _create_trip(client)
77
- r = await client.post(f"/api/v1/trips/{trip_id}/items",
78
- json={"title": "Minimal item", "type": "other"})
79
- assert r.status_code == 201
80
- body = r.json()
81
- assert body["event_at"] is None
82
- assert body["booking_ref"] is None
83
-
84
-
85
- async def test_create_item_with_parsed_data(client):
86
- trip_id = await _create_trip(client)
87
- parsed = {"origin": "FRA", "destination": "JFK", "confidence": 0.95}
88
- r = await client.post(f"/api/v1/trips/{trip_id}/items",
89
- json={**make_item(), "parsed_data": parsed})
90
- assert r.status_code == 201
91
- assert r.json()["parsed_data"]["origin"] == "FRA"
92
-
93
-
94
- # ── update ────────────────────────────────────────────────────────────────────
95
-
96
- async def test_update_item_type(client):
97
- trip_id = await _create_trip(client)
98
- item = await _create_item(client, trip_id)
99
- r = await client.patch(f"/api/v1/trips/{trip_id}/items/{item['id']}",
100
- json={"type": "hotel"})
101
- assert r.status_code == 200
102
- assert r.json()["type"] == "hotel"
103
-
104
-
105
- async def test_update_item_not_found(client):
106
- trip_id = await _create_trip(client)
107
- r = await client.patch(
108
- f"/api/v1/trips/{trip_id}/items/00000000-0000-0000-0000-000000000000",
109
- json={"title": "Ghost"},
110
- )
111
- assert r.status_code == 404
112
-
113
-
114
- async def test_update_item_empty_body_rejected(client):
115
- trip_id = await _create_trip(client)
116
- item = await _create_item(client, trip_id)
117
- r = await client.patch(f"/api/v1/trips/{trip_id}/items/{item['id']}", json={})
118
- assert r.status_code == 400
119
-
120
-
121
- # ── delete ────────────────────────────────────────────────────────────────────
122
-
123
- async def test_delete_item(client):
124
- trip_id = await _create_trip(client)
125
- item = await _create_item(client, trip_id)
126
- r = await client.delete(f"/api/v1/trips/{trip_id}/items/{item['id']}")
127
- assert r.status_code == 204
128
-
129
- items = await client.get(f"/api/v1/trips/{trip_id}/items")
130
- assert items.json() == []
131
-
132
-
133
- async def test_delete_item_not_found(client):
134
- trip_id = await _create_trip(client)
135
- r = await client.delete(
136
- f"/api/v1/trips/{trip_id}/items/00000000-0000-0000-0000-000000000000"
137
- )
138
- assert r.status_code == 404
@@ -1,113 +0,0 @@
1
- """
2
- Tests for /api/v1/mail/import
3
-
4
- Covers:
5
- strip_email_headers – removes From/To/Subject/X-* lines, preserves body
6
- import_mail – text body → inbox when no trip_id
7
- import_mail – text body → trip item when trip_id provided
8
- import_mail – stripped headers don't leak into parsed_data
9
-
10
- Run: pytest tests/test_mail.py -v
11
- """
12
- import pytest
13
- from httpx import AsyncClient
14
-
15
- from app.routers.mail import strip_email_headers
16
- from tests.conftest import make_trip, mock_ollama_flight
17
-
18
- RAW_EMAIL = """\
19
- From: booking@lufthansa.com
20
- To: max@example.com
21
- Subject: Ihre Buchungsbestätigung LH 400
22
- Date: Mon, 12 Aug 2024 09:00:00 +0200
23
- Content-Type: text/plain; charset=utf-8
24
-
25
- Sehr geehrter Herr Mustermann,
26
-
27
- Ihre Buchung wurde bestätigt.
28
- Flug LH 400 Frankfurt (FRA) → New York (JFK)
29
- Abflug: 15. August 2024, 10:30 Uhr
30
- Buchungsreferenz: ABC123
31
- """
32
-
33
-
34
- # ── strip_email_headers (pure, synchronous) ───────────────────────────────────
35
-
36
- def test_strip_removes_from_header():
37
- result = strip_email_headers(RAW_EMAIL)
38
- assert "From:" not in result
39
- assert "To:" not in result
40
-
41
-
42
- def test_strip_removes_subject():
43
- result = strip_email_headers(RAW_EMAIL)
44
- assert "Subject:" not in result
45
-
46
-
47
- def test_strip_preserves_body():
48
- result = strip_email_headers(RAW_EMAIL)
49
- assert "LH 400" in result
50
- assert "ABC123" in result
51
- assert "Mustermann" in result
52
-
53
-
54
- def test_strip_x_headers():
55
- raw = "X-Mailer: Outlook\nX-Spam-Status: No\n\nBody content here."
56
- result = strip_email_headers(raw)
57
- assert "X-Mailer" not in result
58
- assert "Body content here" in result
59
-
60
-
61
- def test_strip_collapses_blank_lines():
62
- raw = "From: a@b.com\n\n\n\n\nBody text."
63
- result = strip_email_headers(raw)
64
- assert "\n\n\n" not in result
65
-
66
-
67
- # ── import_mail endpoint ──────────────────────────────────────────────────────
68
-
69
- @pytest.mark.asyncio
70
- async def test_import_mail_goes_to_inbox_without_trip(client: AsyncClient, mock_ollama_flight):
71
- r = await client.post(
72
- "/api/v1/mail/import",
73
- content=RAW_EMAIL,
74
- headers={"Content-Type": "text/plain"},
75
- )
76
- assert r.status_code == 200
77
- body = r.json()
78
- assert body["parsed"]["type"] == "flight"
79
- assert body["parsed"]["booking_ref"] == "ABC123"
80
-
81
- inbox = await client.get("/api/v1/inbox")
82
- assert inbox.status_code == 200
83
- assert len(inbox.json()) == 1
84
-
85
-
86
- @pytest.mark.asyncio
87
- async def test_import_mail_assigned_to_trip(client: AsyncClient, mock_ollama_flight):
88
- trip_r = await client.post("/api/v1/trips", json=make_trip())
89
- trip_id = trip_r.json()["id"]
90
-
91
- r = await client.post(
92
- f"/api/v1/mail/import?trip_id={trip_id}",
93
- content=RAW_EMAIL,
94
- headers={"Content-Type": "text/plain"},
95
- )
96
- assert r.status_code == 200
97
-
98
- items_r = await client.get(f"/api/v1/trips/{trip_id}/items")
99
- assert len(items_r.json()) == 1
100
-
101
- inbox = await client.get("/api/v1/inbox")
102
- assert inbox.json() == []
103
-
104
-
105
- @pytest.mark.asyncio
106
- async def test_import_mail_low_confidence_still_saves(client: AsyncClient, mock_ollama_low_confidence):
107
- r = await client.post(
108
- "/api/v1/mail/import",
109
- content="Some random text that isn't a booking",
110
- headers={"Content-Type": "text/plain"},
111
- )
112
- assert r.status_code == 200
113
- assert r.json()["parsed"]["confidence"] == 0.1
@@ -1,145 +0,0 @@
1
- """
2
- Unit tests for the Ollama service layer.
3
-
4
- Covers:
5
- parse_text – valid JSON response, malformed JSON fallback, empty response
6
- parse_image – base64 encoding, correct payload shape
7
- _safe_parse – markdown fence stripping, JSON decode errors → fallback dict
8
- confidence – low-confidence items still return a dict (never raise)
9
-
10
- Run: pytest tests/test_ollama.py -v
11
- """
12
-
13
- import json
14
- from unittest.mock import AsyncMock, MagicMock, patch
15
-
16
- import pytest
17
-
18
- from app.services.ollama import _safe_parse, parse_image, parse_text
19
-
20
- # ── _safe_parse (pure, synchronous) ──────────────────────────────────────────
21
-
22
- def test_safe_parse_valid_json():
23
- raw = '{"type": "flight", "title": "LH 400", "confidence": 0.9}'
24
- result = _safe_parse(raw)
25
- assert result["type"] == "flight"
26
- assert result["confidence"] == 0.9
27
-
28
-
29
- def test_safe_parse_strips_markdown_fences():
30
- raw = "```json\n{\"type\": \"hotel\", \"title\": \"Marriott\"}\n```"
31
- result = _safe_parse(raw)
32
- assert result["type"] == "hotel"
33
-
34
-
35
- def test_safe_parse_strips_plain_fences():
36
- raw = "```\n{\"type\": \"train\"}\n```"
37
- assert _safe_parse(raw)["type"] == "train"
38
-
39
-
40
- def test_safe_parse_malformed_returns_fallback():
41
- result = _safe_parse("this is not json at all")
42
- assert result["type"] == "other"
43
- assert result["confidence"] == 0.0
44
- assert "Unrecognized" in result["title"]
45
-
46
-
47
- def test_safe_parse_empty_string_returns_fallback():
48
- result = _safe_parse("")
49
- assert result["type"] == "other"
50
-
51
-
52
- def test_safe_parse_truncates_long_summary():
53
- long_text = "x" * 1000
54
- result = _safe_parse(long_text)
55
- assert len(result.get("raw_summary", "")) <= 300
56
-
57
-
58
- # ── parse_text ────────────────────────────────────────────────────────────────
59
-
60
- @pytest.mark.asyncio
61
- async def test_parse_text_happy_path():
62
- expected = {"type": "flight", "title": "LH 400 FRA→JFK", "confidence": 0.9}
63
- mock_response = MagicMock()
64
- mock_response.json.return_value = {"response": json.dumps(expected)}
65
-
66
- with patch("httpx.AsyncClient") as mock_cls:
67
- mock_client = AsyncMock()
68
- mock_client.__aenter__ = AsyncMock(return_value=mock_client)
69
- mock_client.__aexit__ = AsyncMock(return_value=False)
70
- mock_client.post = AsyncMock(return_value=mock_response)
71
- mock_cls.return_value = mock_client
72
-
73
- result, was_cached = await parse_text("Flight LH 400 from Frankfurt to New York")
74
-
75
- assert result["type"] == "flight"
76
- assert result["confidence"] == 0.9
77
- assert was_cached is False
78
-
79
-
80
- @pytest.mark.asyncio
81
- async def test_parse_text_ollama_returns_garbage():
82
- mock_response = MagicMock()
83
- mock_response.json.return_value = {"response": "I cannot parse this."}
84
-
85
- with patch("httpx.AsyncClient") as mock_cls:
86
- mock_client = AsyncMock()
87
- mock_client.__aenter__ = AsyncMock(return_value=mock_client)
88
- mock_client.__aexit__ = AsyncMock(return_value=False)
89
- mock_client.post = AsyncMock(return_value=mock_response)
90
- mock_cls.return_value = mock_client
91
-
92
- result, was_cached = await parse_text("some text")
93
-
94
- assert result["type"] == "other"
95
- assert result["confidence"] == 0.0
96
- assert was_cached is False
97
-
98
-
99
- @pytest.mark.asyncio
100
- async def test_parse_text_ollama_empty_response():
101
- mock_response = MagicMock()
102
- mock_response.json.return_value = {}
103
-
104
- with patch("httpx.AsyncClient") as mock_cls:
105
- mock_client = AsyncMock()
106
- mock_client.__aenter__ = AsyncMock(return_value=mock_client)
107
- mock_client.__aexit__ = AsyncMock(return_value=False)
108
- mock_client.post = AsyncMock(return_value=mock_response)
109
- mock_cls.return_value = mock_client
110
-
111
- result, was_cached = await parse_text("anything")
112
-
113
- assert isinstance(result, dict)
114
- assert was_cached is False
115
-
116
-
117
- # ── parse_image ───────────────────────────────────────────────────────────────
118
-
119
- @pytest.mark.asyncio
120
- async def test_parse_image_sends_base64():
121
- payload_sent = {}
122
- expected = {"type": "hotel", "title": "Marriott Berlin", "confidence": 0.85}
123
-
124
- async def fake_post(url, json=None, **kwargs):
125
- payload_sent.update(json or {})
126
- mock_resp = MagicMock()
127
- mock_resp.json.return_value = {"response": json_str(expected)}
128
- return mock_resp
129
-
130
- def json_str(d): return __import__("json").dumps(d)
131
-
132
- with patch("httpx.AsyncClient") as mock_cls:
133
- mock_client = AsyncMock()
134
- mock_client.__aenter__ = AsyncMock(return_value=mock_client)
135
- mock_client.__aexit__ = AsyncMock(return_value=False)
136
- mock_client.post = AsyncMock(side_effect=fake_post)
137
- mock_cls.return_value = mock_client
138
-
139
- result, raw, was_cached = await parse_image(b"\xff\xd8\xff", "image/jpeg")
140
-
141
- assert "images" in payload_sent
142
- assert len(payload_sent["images"]) == 1
143
- assert isinstance(payload_sent["images"][0], str)
144
- assert result["type"] == "hotel"
145
- assert was_cached is False
@@ -1,98 +0,0 @@
1
- """
2
- Tests for POST /parse/url endpoint.
3
- httpx is mocked — no real network calls.
4
- """
5
- import json
6
- import pytest
7
- from unittest.mock import AsyncMock, patch, MagicMock
8
-
9
-
10
- FAKE_HTML = """
11
- <html><head><title>Booking Confirmation</title></head>
12
- <body>
13
- <h1>Booking Confirmed</h1>
14
- <p>Flight LH 400 Frankfurt to New York</p>
15
- <p>Booking Reference: XYZ789</p>
16
- <p>Date: 2025-08-15</p>
17
- </body></html>
18
- """
19
-
20
-
21
- @pytest.fixture
22
- def mock_ollama_parse():
23
- with patch("app.services.ollama.parse_text", new_callable=AsyncMock) as m:
24
- m.return_value = (
25
- {
26
- "type": "flight",
27
- "title": "LH 400 Frankfurt → New York",
28
- "booking_ref": "XYZ789",
29
- "provider": "Lufthansa",
30
- "event_at": "2025-08-15T10:30:00",
31
- "confidence": 0.88,
32
- },
33
- False,
34
- )
35
- yield m
36
-
37
-
38
- @pytest.fixture
39
- def mock_http_response():
40
- mock_resp = MagicMock()
41
- mock_resp.headers = {"content-type": "text/html; charset=utf-8"}
42
- mock_resp.text = FAKE_HTML
43
- mock_resp.raise_for_status = MagicMock()
44
- return mock_resp
45
-
46
-
47
- @pytest.mark.asyncio
48
- async def test_parse_url_to_inbox(client, mock_ollama_parse, mock_http_response):
49
- with patch("httpx.AsyncClient") as mock_client_cls:
50
- mock_client_instance = AsyncMock()
51
- mock_client_instance.get = AsyncMock(return_value=mock_http_response)
52
- mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance)
53
- mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)
54
-
55
- resp = await client.post("/api/v1/parse/url", json={"url": "https://example.com/booking"})
56
-
57
- assert resp.status_code == 200
58
- data = resp.json()
59
- assert data["parsed"]["type"] == "flight"
60
- assert data["parsed"]["booking_ref"] == "XYZ789"
61
-
62
-
63
- @pytest.mark.asyncio
64
- async def test_parse_url_to_trip(client, mock_ollama_parse, mock_http_response):
65
- # Create a trip first
66
- trip_resp = await client.post("/api/v1/trips", json={"name": "Test Trip"})
67
- assert trip_resp.status_code == 201
68
- trip_id = trip_resp.json()["id"]
69
-
70
- with patch("httpx.AsyncClient") as mock_client_cls:
71
- mock_client_instance = AsyncMock()
72
- mock_client_instance.get = AsyncMock(return_value=mock_http_response)
73
- mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance)
74
- mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)
75
-
76
- resp = await client.post(
77
- "/api/v1/parse/url",
78
- json={"url": "https://example.com/booking", "trip_id": trip_id},
79
- )
80
-
81
- assert resp.status_code == 200
82
- data = resp.json()
83
- assert data["parsed"]["type"] == "flight"
84
-
85
-
86
- @pytest.mark.asyncio
87
- async def test_parse_url_unreachable_returns_422(client):
88
- import httpx
89
- with patch("httpx.AsyncClient") as mock_client_cls:
90
- mock_client_instance = AsyncMock()
91
- mock_client_instance.get = AsyncMock(side_effect=httpx.ConnectError("refused"))
92
- mock_client_cls.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance)
93
- mock_client_cls.return_value.__aexit__ = AsyncMock(return_value=False)
94
-
95
- resp = await client.post("/api/v1/parse/url", json={"url": "https://unreachable.invalid"})
96
-
97
- assert resp.status_code == 422
98
- assert "Could not fetch URL" in resp.json()["detail"]
@@ -1,72 +0,0 @@
1
- """
2
- Unit tests for app/services/parser.py
3
-
4
- Covers:
5
- extract_text_from_pdf – real minimal PDF bytes, multi-page join
6
- is_image / is_pdf / is_text – mime type routing guards
7
-
8
- Run: pytest tests/test_parser.py -v
9
- """
10
-
11
- import pytest
12
- from app.services.parser import extract_text_from_pdf, is_image, is_pdf, is_text
13
-
14
-
15
- # ── MIME type guards ───────────────────────────────────────────────────────────
16
-
17
- def test_is_pdf():
18
- assert is_pdf("application/pdf") is True
19
- assert is_pdf("application/octet-stream") is False
20
- assert is_pdf("image/png") is False
21
-
22
-
23
- def test_is_image():
24
- assert is_image("image/jpeg") is True
25
- assert is_image("image/png") is True
26
- assert is_image("image/webp") is True
27
- assert is_image("application/pdf") is False
28
- assert is_image("text/plain") is False
29
-
30
-
31
- def test_is_text():
32
- assert is_text("text/plain") is True
33
- assert is_text("text/html") is True
34
- assert is_text("text/csv") is True
35
- assert is_text("application/pdf") is False
36
- assert is_text("image/jpeg") is False
37
-
38
-
39
- # ── PDF extraction ─────────────────────────────────────────────────────────────
40
-
41
- def _minimal_pdf(text: str) -> bytes:
42
- """Creates a minimal valid 1-page PDF containing `text`."""
43
- import fitz
44
- doc = fitz.open()
45
- page = doc.new_page()
46
- page.insert_text((72, 72), text)
47
- return doc.tobytes()
48
-
49
-
50
- def test_extract_text_from_pdf_single_page():
51
- content = _minimal_pdf("Booking confirmation LH400")
52
- result = extract_text_from_pdf(content)
53
- assert "LH400" in result
54
- assert "Booking" in result
55
-
56
-
57
- def test_extract_text_from_pdf_strips_whitespace():
58
- content = _minimal_pdf(" some text ")
59
- result = extract_text_from_pdf(content)
60
- assert result == result.strip()
61
-
62
-
63
- def test_extract_text_from_pdf_multi_page():
64
- import fitz
65
- doc = fitz.open()
66
- doc.new_page().insert_text((72, 72), "Page one content")
67
- doc.new_page().insert_text((72, 72), "Page two content")
68
- content = doc.tobytes()
69
-
70
- result = extract_text_from_pdf(content)
71
- assert "Page one" in result
72
- assert "Page two" in result