failproofai 1.0.12-beta.2 → 1.0.12
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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00l2u6j86cb8y._.js → [root-of-the-server]__167mipqmjprq5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1fw0_nzkae5ao._.js → _1d98fyfrcaqhy._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1fv03uigni8s5.js → node_modules_html-to-image_es_index_0tqc05n832v7a.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/preview-props.json +3 -3
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/{1c_jf6g5wnslk.js → 0el9n3h9_zp5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/0k7g_8x3o6gd-.js +1 -0
- package/.next/standalone/.next/static/chunks/{2o6d37j425uci.js → 0umzct31duk5g.js} +1 -1
- package/.next/standalone/.next/static/chunks/{42xptz88mtq3x.js → 0wobqgosirs8p.js} +1 -1
- package/.next/standalone/.next/static/chunks/12rug3jilwqzs.js +1 -0
- package/.next/standalone/.next/static/chunks/{0ay__-d2h1xb5.js → 1kduj74-715jh.js} +1 -1
- package/.next/standalone/.next/static/chunks/{40oworn_-x12p.js → 2701amhrbsbot.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0tbm3vjm62h-2.js → 27cavoc8paelg.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1my3-5p47t7r-.js → 2ve23a7keupjw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{21hckpmsc4g70.js → 3bv6xaoitjpnh.js} +2 -2
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +19 -0
- package/.next/standalone/fp-cloud-cli/README.md +120 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/app.py +15 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/client.py +198 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/cloud_output.py +523 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_cloud.py +45 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/_write.py +3 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/account_cmds.py +171 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/agent_cmds.py +80 -7
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/alerts_cmds.py +23 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/audits_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/contracts_cmds.py +67 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/dashboards_cmds.py +159 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/evaluators_cmds.py +380 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/events_cmds.py +14 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/incidents_cmds.py +82 -6
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/list_cmds.py +4 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/orgs_cmds.py +17 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +143 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/queries_cmds.py +70 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sentiment_cmds.py +49 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/sessions_cmds.py +95 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/settings_cmds.py +2 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/usage_cmds.py +21 -1
- package/.next/standalone/fp-cloud-cli/fp_cli/commands/users_cmds.py +14 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/errors.py +3 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/evaluator_spec.py +266 -0
- package/.next/standalone/fp-cloud-cli/fp_cli/output.py +187 -81
- package/.next/standalone/fp-cloud-cli/fp_cli/theme.py +21 -24
- package/.next/standalone/fp-cloud-cli/skill/SKILL.md +124 -23
- package/.next/standalone/fp-cloud-cli/skill/agents/openai.yaml +2 -1
- package/.next/standalone/fp-cloud-cli/skill/references/commands.md +168 -16
- package/.next/standalone/fp-cloud-cli/tests/fake_cloud_smoke.py +312 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_account.py +125 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_agent.py +253 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_human_views.py +287 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_observability.py +116 -0
- package/.next/standalone/fp-cloud-cli/tests/test_cloud_transport.py +141 -0
- package/.next/standalone/fp-cloud-cli/tests/test_commands.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_dashboards_agent.py +3 -3
- package/.next/standalone/fp-cloud-cli/tests/test_evaluator_authoring.py +449 -0
- package/.next/standalone/fp-cloud-cli/tests/test_facets.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_help_table_coverage.py +15 -0
- package/.next/standalone/fp-cloud-cli/tests/test_operator.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_orgs.py +1 -1
- package/.next/standalone/fp-cloud-cli/tests/test_output.py +37 -37
- package/.next/standalone/fp-cloud-cli/tests/test_round4_behaviour.py +144 -0
- package/.next/standalone/fp-cloud-cli/tests/test_screen_language.py +80 -0
- package/.next/standalone/fp-cloud-cli/tests/test_session_reevaluate.py +33 -0
- package/.next/standalone/fp-cloud-cli/tests/test_staging_parity.py +198 -0
- package/.next/standalone/fp-cloud-cli/tests/test_whoami.py +1 -1
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +68 -176
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-6/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/ai-7/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +98 -115
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +3 -3
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package-lock.json +149 -171
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.11/package.json +2 -2
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package-lock.json +4 -4
- package/.next/standalone/sdk/typescript/integration/fixtures/llamaindex-0.12/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +25 -25
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/mastra-1/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package-lock.json +10 -10
- package/.next/standalone/sdk/typescript/integration/fixtures/nextjs/package.json +1 -1
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package-lock.json +7 -7
- package/.next/standalone/sdk/typescript/integration/fixtures/runtimes/package.json +1 -1
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +85 -37
- package/dist/worker.mjs +73 -28
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +9 -9
- package/src/hooks/agent-skill.ts +3 -0
- package/src/hooks/fp-home.ts +1 -1
- package/src/hooks/handler.ts +22 -3
- package/src/hooks/semantic/intent.ts +127 -24
- package/src/hooks/semantic/jev-review.ts +21 -2
- package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +0 -1
- package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +0 -1
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{LHCtFxVG0mdvd4lsXAOFC → D7LY1YSrw96ZxKSpxxrFw}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import respx
|
|
5
|
+
|
|
6
|
+
from fp_cli.app import app
|
|
7
|
+
|
|
8
|
+
BASE = "http://dash.test"
|
|
9
|
+
DASHBOARD = {"name": "Quality", "description": "Keep", "definition": {"filters": {"env": "prod"}}}
|
|
10
|
+
TILE = {"id": "t1", "query_id": "q1", "title": "Before", "chart_type": "table",
|
|
11
|
+
"chart_config": {}, "param_bindings": {}, "pos_x": 0, "pos_y": 0, "pos_w": 6, "pos_h": 4}
|
|
12
|
+
RESULT = {"columns": [{"name": "agent", "type": "String"}, {"name": "n", "type": "Int64"}],
|
|
13
|
+
"rows": [["agent-001", 42]], "truncated": False, "elapsed_ms": 1}
|
|
14
|
+
CASES = [
|
|
15
|
+
(["dashboards", "list"], "GET", "/dashboards", {"dashboards": []}),
|
|
16
|
+
(["dashboards", "show", "d1"], "GET", "/dashboards/d1", {"dashboard": DASHBOARD, "tiles": []}),
|
|
17
|
+
(["dashboards", "create", "--name", "Quality"], "POST", "/dashboards", {"id": "d1"}),
|
|
18
|
+
(["dashboards", "update", "d1", "--name", "New"], "PUT", "/dashboards/d1", {"id": "d1"}),
|
|
19
|
+
(["dashboards", "delete", "d1"], "DELETE", "/dashboards/d1", {"deleted": True}),
|
|
20
|
+
(["dashboards", "tiles", "d1", "list"], "GET", "/dashboards/d1/tiles", {"tiles": [TILE]}),
|
|
21
|
+
(["dashboards", "tiles", "d1", "add", "--query", "q1", "--chart", "bar"], "POST", "/dashboards/d1/tiles", {"id": "t1"}),
|
|
22
|
+
(["dashboards", "tiles", "d1", "update", "t1", "--title", "After"], "PUT", "/dashboards/d1/tiles/t1", {"id": "t1"}),
|
|
23
|
+
(["dashboards", "tiles", "d1", "remove", "t1"], "DELETE", "/dashboards/d1/tiles/t1", {"deleted": True}),
|
|
24
|
+
(["dashboards", "tiles", "d1", "layout", "--file", "{layout}"], "PUT", "/dashboards/d1/tiles/layout", {"updated": 1}),
|
|
25
|
+
(["sessions", "show", "s1"], "GET", "/events/summary", {"events": [], "next_cursor": None}),
|
|
26
|
+
(["sessions", "export", "s1"], "GET", "/sessions/s1/export", {"session_id": "s1", "events": []}),
|
|
27
|
+
(["sessions", "history", "s1"], "GET", "/sessions/s1/evaluation-history", []),
|
|
28
|
+
(["events", "latency"], "GET", "/events/latency_aggregate", {"p95": 42}),
|
|
29
|
+
(["sentiment", "summary"], "GET", "/events/sentiment", {"positive": 1}),
|
|
30
|
+
(["sentiment", "messages"], "GET", "/events/sentiment/messages", []),
|
|
31
|
+
(["sentiment", "sessions"], "GET", "/events/sentiment/sessions", []),
|
|
32
|
+
(["sentiment", "session", "s1"], "GET", "/events/sentiment/sessions/s1", {}),
|
|
33
|
+
(["query", "export", "q1", "--format", "json"], "POST", "/queries/run", RESULT),
|
|
34
|
+
(["query", "export", "--sql", "SELECT 1", "--format", "csv"], "POST", "/queries/run", RESULT),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def files(tmp_path):
|
|
40
|
+
path = tmp_path / "layout.json"
|
|
41
|
+
path.write_text(json.dumps({"tiles": [{k: v for k, v in TILE.items() if k in ("id", "pos_x", "pos_y", "pos_w", "pos_h")}]}))
|
|
42
|
+
return {"layout": str(path)}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
46
|
+
@pytest.mark.parametrize("argv,method,path,response", CASES, ids=lambda v: " ".join(v) if isinstance(v, list) else str(v))
|
|
47
|
+
def test_observability_commands(logged_in, runner, files, key_mode, argv, method, path, response):
|
|
48
|
+
prefix = "/v1" if key_mode else "/api"
|
|
49
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
50
|
+
mock.get(f"{BASE}{prefix}/dashboards/d1").respond(200, json={"dashboard": DASHBOARD, "tiles": []})
|
|
51
|
+
mock.get(f"{BASE}{prefix}/dashboards/d1/tiles").respond(200, json={"tiles": [TILE]})
|
|
52
|
+
mock.get(f"{BASE}{prefix}/sessions").respond(200, json={"sessions": [{"session_id": "s1"}]})
|
|
53
|
+
route = mock.request(method, f"{BASE}{prefix}{path}").respond(200, json=response)
|
|
54
|
+
result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), *[v.format(**files) for v in argv]])
|
|
55
|
+
assert result.exit_code == 0, result.output
|
|
56
|
+
assert route.called
|
|
57
|
+
json.loads(result.stdout)
|
|
58
|
+
if argv[:2] == ["dashboards", "create"]:
|
|
59
|
+
assert json.loads(route.calls[0].request.content) == {"name": "Quality", "description": "", "definition": {}}
|
|
60
|
+
if argv[:2] == ["dashboards", "update"]:
|
|
61
|
+
assert json.loads(route.calls[0].request.content) == {**DASHBOARD, "name": "New"}
|
|
62
|
+
if argv[:4] == ["dashboards", "tiles", "d1", "update"]:
|
|
63
|
+
assert json.loads(route.calls[0].request.content) == {k: ("After" if k == "title" else v) for k, v in TILE.items() if k != "id"}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.mark.parametrize("argv,method,path,response", CASES)
|
|
67
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
68
|
+
def test_observability_404(logged_in, runner, files, key_mode, argv, method, path, response):
|
|
69
|
+
with respx.mock as mock:
|
|
70
|
+
mock.route().respond(404, json={"error": "unknown route"})
|
|
71
|
+
result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), *[v.format(**files) for v in argv]])
|
|
72
|
+
assert result.exit_code == 6, result.output
|
|
73
|
+
assert "not available on this FailproofAI Cloud yet" in result.stdout
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_query_compose(logged_in, runner):
|
|
77
|
+
stream = 'data: {"type":"done","sql":"SELECT 1"}\n\n'
|
|
78
|
+
with respx.mock as mock:
|
|
79
|
+
draft = mock.post(f"{BASE}/api/agent/compose-sql").respond(200, text=stream, headers={"content-type": "text/event-stream"})
|
|
80
|
+
run = mock.post(f"{BASE}/api/queries/run").respond(200, json=RESULT)
|
|
81
|
+
result = runner.invoke(app, ["--json", "query", "compose", "Count events", "--run"])
|
|
82
|
+
assert result.exit_code == 0, result.output
|
|
83
|
+
assert json.loads(draft.calls[0].request.content) == {"intent": "Count events"}
|
|
84
|
+
assert json.loads(run.calls[0].request.content) == {"sql": "SELECT 1", "params": []}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_query_compose_key_denial(logged_in, runner):
|
|
88
|
+
with respx.mock as mock:
|
|
89
|
+
result = runner.invoke(app, ["--json", "--api-key", "key", "query", "compose", "Count events"])
|
|
90
|
+
assert not mock.calls
|
|
91
|
+
assert result.exit_code == 2
|
|
92
|
+
assert "fp login" in result.stdout
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_exports_write_files(logged_in, runner, tmp_path):
|
|
96
|
+
path = tmp_path / "data.csv"
|
|
97
|
+
with respx.mock as mock:
|
|
98
|
+
mock.post(f"{BASE}/api/queries/run").respond(200, json=RESULT)
|
|
99
|
+
result = runner.invoke(app, ["--json", "query", "export", "--sql", "SELECT 1", "--out", str(path)])
|
|
100
|
+
assert result.exit_code == 0
|
|
101
|
+
assert path.read_text().splitlines() == ["agent,n", "agent-001,42"]
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize("argv", [
|
|
105
|
+
["dashboards", "create", "--name", ""],
|
|
106
|
+
["dashboards", "update", "d1"],
|
|
107
|
+
["dashboards", "tiles", "d1", "add", "--query", "q1", "--chart", "invalid"],
|
|
108
|
+
["sentiment", "messages", "--limit", "0"],
|
|
109
|
+
["query", "export", "--sql", "SELECT 1", "--format", "xml"],
|
|
110
|
+
["query", "export", "q1", "--sql", "SELECT 1"],
|
|
111
|
+
])
|
|
112
|
+
def test_validation(logged_in, runner, argv):
|
|
113
|
+
with respx.mock as mock:
|
|
114
|
+
result = runner.invoke(app, ["--json", *argv])
|
|
115
|
+
assert not mock.calls
|
|
116
|
+
assert result.exit_code == 2, result.output
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import respx
|
|
3
|
+
|
|
4
|
+
from fp_cli import client
|
|
5
|
+
from fp_cli.errors import NotFoundError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.mark.parametrize("family", [
|
|
9
|
+
"evaluator-definitions", "evaluator-instances", "evaluator-schema-profile",
|
|
10
|
+
"evaluation-jobs", "dashboards", "otel", "offers", "team-trial",
|
|
11
|
+
])
|
|
12
|
+
def test_added_v1_families(family):
|
|
13
|
+
assert client._v1_path(f"/api/{family}") == f"/v1/{family}"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.parametrize("method", ["GET", "POST", "PATCH", "DELETE"])
|
|
17
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
18
|
+
def test_cloud_request_degrades(method, key_mode):
|
|
19
|
+
ctx = client.ClientContext(
|
|
20
|
+
"http://dash.test", token=None if key_mode else "tok",
|
|
21
|
+
api_key="key" if key_mode else None,
|
|
22
|
+
auth_mode=client.AuthMode.API_KEY if key_mode else client.AuthMode.SESSION,
|
|
23
|
+
)
|
|
24
|
+
prefix = "v1" if key_mode else "api"
|
|
25
|
+
with respx.mock:
|
|
26
|
+
route = respx.request(method, f"http://dash.test/{prefix}/dashboards").respond(404, json={})
|
|
27
|
+
with pytest.raises(NotFoundError, match="not available on this FailproofAI Cloud yet") as exc:
|
|
28
|
+
client.cloud_request(ctx, method, "/api/dashboards")
|
|
29
|
+
assert exc.value.exit_code == 6
|
|
30
|
+
headers = route.calls[0].request.headers
|
|
31
|
+
assert ("authorization" in headers) is key_mode
|
|
32
|
+
assert ("cookie" in headers) is not key_mode
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_new_capability_html_404_degrades():
|
|
36
|
+
ctx = client.ClientContext("http://dash.test", api_key="key", auth_mode=client.AuthMode.API_KEY)
|
|
37
|
+
with respx.mock as mock:
|
|
38
|
+
mock.get("http://dash.test/v1/dashboards").respond(404, text="<html>old Cloud</html>", headers={"content-type": "text/html"})
|
|
39
|
+
with pytest.raises(NotFoundError, match="not available on this FailproofAI Cloud yet") as exc:
|
|
40
|
+
client.cloud_request(ctx, "GET", "/api/dashboards")
|
|
41
|
+
assert exc.value.exit_code == 6
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
45
|
+
@pytest.mark.parametrize("method,path,body,params", [
|
|
46
|
+
("PATCH", "/api/evaluator-definitions/stale-id", {"enabled": False}, None),
|
|
47
|
+
("GET", "/api/sessions/missing/export", None, None),
|
|
48
|
+
("POST", "/api/sessions/missing/re-evaluate", None, None),
|
|
49
|
+
("GET", "/api/dashboards/missing/tiles", None, None),
|
|
50
|
+
("DELETE", "/api/dashboards/board/tiles/stale-tile", None, None),
|
|
51
|
+
("GET", "/api/events/sentiment/sessions/missing", None, None),
|
|
52
|
+
("GET", "/api/audits/contracts/stale-id", None, None),
|
|
53
|
+
("POST", "/api/queries/run", {"query_id": "missing"}, None),
|
|
54
|
+
("POST", "/api/agent/answer", {"callId": "missing", "decision": "reject"}, None),
|
|
55
|
+
("DELETE", "/api/settings/model-context-windows", None, {"model": "missing"}),
|
|
56
|
+
])
|
|
57
|
+
def test_record_404_is_not_route_unavailable(key_mode, method, path, body, params):
|
|
58
|
+
ctx = client.ClientContext(
|
|
59
|
+
"http://dash.test", token=None if key_mode else "tok",
|
|
60
|
+
api_key="key" if key_mode else None,
|
|
61
|
+
auth_mode=client.AuthMode.API_KEY if key_mode else client.AuthMode.SESSION,
|
|
62
|
+
)
|
|
63
|
+
if key_mode and path.startswith("/api/agent/"):
|
|
64
|
+
pytest.skip("the agent has no key route; tested via session-only command guards")
|
|
65
|
+
wire_path = client._v1_path(path) if key_mode else path
|
|
66
|
+
with respx.mock as mock:
|
|
67
|
+
mock.request(method, f"http://dash.test{wire_path}").respond(404, json={})
|
|
68
|
+
with pytest.raises(NotFoundError) as exc:
|
|
69
|
+
client.cloud_request(ctx, method, path, body=body, params=params)
|
|
70
|
+
assert exc.value.exit_code == 6
|
|
71
|
+
assert "Record not found" in exc.value.message
|
|
72
|
+
assert "not available on this FailproofAI Cloud yet" not in exc.value.message
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@pytest.mark.parametrize("response,route_unknown", [
|
|
76
|
+
({"code": "route_not_found"}, True),
|
|
77
|
+
({"error": {"code": "route_not_found"}}, True),
|
|
78
|
+
({"error": "unknown endpoint"}, True),
|
|
79
|
+
({"message": "Cannot PATCH /evaluator-definitions/stale-id"}, True),
|
|
80
|
+
({"error": "route does not exist"}, True),
|
|
81
|
+
({"error": "old Cloud route absent"}, True),
|
|
82
|
+
({"error": "evaluator definition not found"}, False),
|
|
83
|
+
({"error": "session not found"}, False),
|
|
84
|
+
({"error": "query not found"}, False),
|
|
85
|
+
({"error": "not available in demo"}, False),
|
|
86
|
+
])
|
|
87
|
+
def test_404_response_overrides_request_shape(response, route_unknown):
|
|
88
|
+
ctx = client.ClientContext("http://dash.test", token="tok")
|
|
89
|
+
with respx.mock as mock:
|
|
90
|
+
mock.patch("http://dash.test/api/evaluator-definitions/stale-id").respond(404, json=response)
|
|
91
|
+
with pytest.raises(NotFoundError) as exc:
|
|
92
|
+
client.cloud_request(ctx, "PATCH", "/api/evaluator-definitions/stale-id", body={"enabled": False})
|
|
93
|
+
assert exc.value.exit_code == 6
|
|
94
|
+
if route_unknown:
|
|
95
|
+
assert exc.value.message == "not available on this FailproofAI Cloud yet"
|
|
96
|
+
else:
|
|
97
|
+
assert exc.value.message == response.get("error", response.get("message"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.parametrize("body", [{"code": "record_not_found"}, {"error": "resource_not_found"}])
|
|
101
|
+
def test_record_error_code_on_collection_is_not_unavailable(body):
|
|
102
|
+
ctx = client.ClientContext("http://dash.test", token="tok")
|
|
103
|
+
with respx.mock as mock:
|
|
104
|
+
mock.post("http://dash.test/api/queries/run").respond(404, json=body)
|
|
105
|
+
with pytest.raises(NotFoundError, match="Record not found") as exc:
|
|
106
|
+
client.cloud_request(ctx, "POST", "/api/queries/run", body={"sql": "SELECT 1"})
|
|
107
|
+
assert "not available" not in exc.value.message
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_collection_with_missing_saved_query_preserves_server_error():
|
|
111
|
+
ctx = client.ClientContext("http://dash.test", token="tok")
|
|
112
|
+
with respx.mock as mock:
|
|
113
|
+
mock.post("http://dash.test/api/queries/run").respond(404, json={"error": "saved query not found"})
|
|
114
|
+
with pytest.raises(NotFoundError, match="saved query not found"):
|
|
115
|
+
client.cloud_request(ctx, "POST", "/api/queries/run", body={"query_id": "stale"})
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_stream_404_distinguishes_route_and_record():
|
|
119
|
+
ctx = client.ClientContext("http://dash.test", token="tok")
|
|
120
|
+
for error, expected in [
|
|
121
|
+
({"code": "unknown_route"}, "not available on this FailproofAI Cloud yet"),
|
|
122
|
+
({"error": "conversation not found"}, "conversation not found"),
|
|
123
|
+
]:
|
|
124
|
+
with respx.mock as mock:
|
|
125
|
+
mock.post("http://dash.test/api/agent/chat").respond(404, json=error)
|
|
126
|
+
with pytest.raises(NotFoundError, match=expected):
|
|
127
|
+
list(client.cloud_stream(ctx, "/api/agent/chat", {"conversationId": "missing"}))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.mark.parametrize("path", [
|
|
131
|
+
"/api/agent/conversations/stale",
|
|
132
|
+
"/api/offers/stale",
|
|
133
|
+
"/api/integrations/slack/stale",
|
|
134
|
+
])
|
|
135
|
+
def test_named_endpoint_families_do_not_hide_record_404s(path):
|
|
136
|
+
ctx = client.ClientContext("http://dash.test", token="tok")
|
|
137
|
+
with respx.mock as mock:
|
|
138
|
+
mock.get(f"http://dash.test{path}").respond(404, json={})
|
|
139
|
+
with pytest.raises(NotFoundError, match="Record not found") as exc:
|
|
140
|
+
client.cloud_request(ctx, "GET", path)
|
|
141
|
+
assert "not available" not in exc.value.message
|
|
@@ -522,7 +522,7 @@ def test_sessions_table(logged_in, runner):
|
|
|
522
522
|
)
|
|
523
523
|
result = runner.invoke(app, ["sessions"])
|
|
524
524
|
assert result.exit_code == 0, result.output
|
|
525
|
-
assert "
|
|
525
|
+
assert "SESSIONS" in result.stdout
|
|
526
526
|
assert "done" in result.stdout # status flattened up from latest_evaluation
|
|
527
527
|
|
|
528
528
|
|
|
@@ -679,7 +679,7 @@ def test_help_command(home, runner):
|
|
|
679
679
|
result = runner.invoke(app, ["help"])
|
|
680
680
|
assert result.exit_code == 0
|
|
681
681
|
out = result.stdout
|
|
682
|
-
assert "
|
|
682
|
+
assert "COMMANDS" in out
|
|
683
683
|
# the four purpose groups, in order
|
|
684
684
|
for g in ("ESSENTIALS", "OBSERVE", "MANAGE", "TOOLS"):
|
|
685
685
|
assert g in out
|
|
@@ -744,7 +744,7 @@ def test_fields_unknown_is_usage_error(logged_in, runner):
|
|
|
744
744
|
def test_dash_h_alias_shows_help(home, runner):
|
|
745
745
|
result = runner.invoke(app, ["-h"])
|
|
746
746
|
assert result.exit_code == 0
|
|
747
|
-
assert "
|
|
747
|
+
assert "COMMANDS" in result.stdout and "sessions" in result.stdout
|
|
748
748
|
assert "ESSENTIALS" in result.stdout # the grouped top-level help, via the rich_format_help override
|
|
749
749
|
|
|
750
750
|
|
|
@@ -171,7 +171,7 @@ def test_agent_health_renders_configured_card(logged_in, runner):
|
|
|
171
171
|
result = runner.invoke(app, ["agent", "health"])
|
|
172
172
|
assert result.exit_code == 0, result.output
|
|
173
173
|
out = result.stdout
|
|
174
|
-
assert "
|
|
174
|
+
assert "ASSISTANT" in out and "configured" in out and "claude-x" in out
|
|
175
175
|
|
|
176
176
|
|
|
177
177
|
@respx.mock
|
|
@@ -192,7 +192,7 @@ def test_agent_models_renders_default_marker(logged_in, runner):
|
|
|
192
192
|
result = runner.invoke(app, ["agent", "models"])
|
|
193
193
|
assert result.exit_code == 0, result.output
|
|
194
194
|
out = result.stdout
|
|
195
|
-
assert "
|
|
195
|
+
assert "MODELS" in out and "m-default" in out and "m-fast" in out and "default" in out
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
@respx.mock
|
|
@@ -213,7 +213,7 @@ def test_agent_chats_renders_table(logged_in, runner):
|
|
|
213
213
|
)
|
|
214
214
|
result = runner.invoke(app, ["agent", "chats"])
|
|
215
215
|
assert result.exit_code == 0, result.output
|
|
216
|
-
assert "
|
|
216
|
+
assert "CHATS" in result.stdout and "perf review" in result.stdout
|
|
217
217
|
assert "chat-id" in result.stdout and "c1" in result.stdout # full chat-id column by default
|
|
218
218
|
assert "open one with" not in (result.stderr or "") # footer removed
|
|
219
219
|
|