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
|
@@ -51,7 +51,7 @@ def test_print_table_writes_to_stdout(capsys):
|
|
|
51
51
|
output.configure(no_color=True, quiet=False)
|
|
52
52
|
output.print_table(["a", "b"], [["1", "2"]], title="Demo")
|
|
53
53
|
out = capsys.readouterr().out
|
|
54
|
-
assert "
|
|
54
|
+
assert "DEMO" in out
|
|
55
55
|
assert "1" in out and "2" in out
|
|
56
56
|
|
|
57
57
|
|
|
@@ -91,7 +91,7 @@ def test_render_events_box_to_stdout(capsys):
|
|
|
91
91
|
output.render_events([_event("2026-06-22T12:21:54Z")], order="desc")
|
|
92
92
|
captured = capsys.readouterr()
|
|
93
93
|
# The box (human data view) goes to STDOUT; the title carries count, direction and date.
|
|
94
|
-
assert "
|
|
94
|
+
assert "EVENTS" in captured.out
|
|
95
95
|
assert "newest first" in captured.out
|
|
96
96
|
assert "2026-06-22" in captured.out
|
|
97
97
|
assert captured.err == ""
|
|
@@ -182,7 +182,7 @@ def test_status_color_enum():
|
|
|
182
182
|
|
|
183
183
|
|
|
184
184
|
def test_short_session():
|
|
185
|
-
assert output._short_session("sess-20260615-fcf97e01") == "sess
|
|
185
|
+
assert output._short_session("sess-20260615-fcf97e01") == "sess-20260615-fcf97e01"
|
|
186
186
|
assert output._short_session("sess-20260615-fcf97e01", full=True) == "sess-20260615-fcf97e01"
|
|
187
187
|
assert output._short_session("nonnum-3-b9d9de") == "nonnum-3-b9d9de" # short → intact
|
|
188
188
|
assert output._short_session("short") == "short"
|
|
@@ -208,9 +208,9 @@ def test_render_sessions_box_has_no_scores(capsys):
|
|
|
208
208
|
_wide_stdout()
|
|
209
209
|
output.render_sessions([_sample_eval()])
|
|
210
210
|
out = capsys.readouterr().out
|
|
211
|
-
assert "
|
|
211
|
+
assert "SESSIONS" in out and "newest first" in out and "2026-06-22" in out
|
|
212
212
|
assert "agent-codegen" in out and "done" in out
|
|
213
|
-
assert "12:56:31" in out and "sess
|
|
213
|
+
assert "12:56:31" in out and "sess-20260615-fcf97e01" in out
|
|
214
214
|
assert "coherence" not in out and "scores" not in out # scores moved to evals
|
|
215
215
|
output.configure(no_color=True, quiet=False)
|
|
216
216
|
|
|
@@ -219,8 +219,8 @@ def test_render_evals_box_has_scores(capsys):
|
|
|
219
219
|
_wide_stdout()
|
|
220
220
|
output.render_evals([_sample_eval()])
|
|
221
221
|
out = capsys.readouterr().out
|
|
222
|
-
assert "
|
|
223
|
-
assert "agent-codegen" in out and "sess
|
|
222
|
+
assert "EVALS" in out and "scores" in out and "coherence" in out
|
|
223
|
+
assert "agent-codegen" in out and "sess-20260615-fcf97e01" in out
|
|
224
224
|
output.configure(no_color=True, quiet=False)
|
|
225
225
|
|
|
226
226
|
|
|
@@ -296,7 +296,7 @@ def test_render_sessions_expanded_lists_full_roster(capsys):
|
|
|
296
296
|
assert "agent-testgen" in out and "9 ev" in out
|
|
297
297
|
assert "●" not in out # uniform: no special "root" marker
|
|
298
298
|
assert "├" in out and "└" in out # uniform tree glyphs, └ closes the list
|
|
299
|
-
assert "
|
|
299
|
+
assert "SESSIONS · 1" in out # count = sessions, not rows
|
|
300
300
|
output.configure(no_color=True, quiet=False)
|
|
301
301
|
|
|
302
302
|
|
|
@@ -403,8 +403,8 @@ def test_render_errors_box(capsys):
|
|
|
403
403
|
_wide_stdout()
|
|
404
404
|
output.render_errors([_errevent("error", "RateLimitError: upstream timed out")])
|
|
405
405
|
out = capsys.readouterr().out
|
|
406
|
-
assert "
|
|
407
|
-
assert "agent-orderbot" in out and "sess
|
|
406
|
+
assert "ERRORS" in out and "newest first" in out
|
|
407
|
+
assert "agent-orderbot" in out and "sess-20260622-4b90b240" in out
|
|
408
408
|
assert "RateLimitError: upstream timed out" in out # server-computed summary field
|
|
409
409
|
assert "17:51:34" in out
|
|
410
410
|
output.configure(no_color=True, quiet=False)
|
|
@@ -479,7 +479,7 @@ def test_render_org_perms(capsys):
|
|
|
479
479
|
out = capsys.readouterr().out
|
|
480
480
|
# header leads with the org name + slug + role; the count moved into the box title
|
|
481
481
|
assert "Globex Corp" in out and "globex" in out and "role admin" in out
|
|
482
|
-
assert "
|
|
482
|
+
assert "PERMISSIONS · 5 · Globex Corp" in out
|
|
483
483
|
assert "dashboards" in out and "keys" in out and "agent" in out
|
|
484
484
|
output.configure(no_color=True, quiet=False)
|
|
485
485
|
|
|
@@ -549,7 +549,7 @@ def test_render_value_list_narrow_caps_columns(capsys):
|
|
|
549
549
|
|
|
550
550
|
|
|
551
551
|
def test_short_id():
|
|
552
|
-
assert output._short_id("1f58376d-7947-9826") == "
|
|
552
|
+
assert output._short_id("1f58376d-7947-9826") == "1f58376d-7947-9826"
|
|
553
553
|
assert output._short_id("short") == "short"
|
|
554
554
|
|
|
555
555
|
|
|
@@ -585,7 +585,7 @@ def test_render_keys_box_and_footer(capsys):
|
|
|
585
585
|
def test_render_keys_show_id(capsys):
|
|
586
586
|
_wide_stdout()
|
|
587
587
|
output.render_keys([ApiKey(id="1f58376d-7947-9826", name="admin", created_at="2026-06-18T05:14:00Z")], show_id=True)
|
|
588
|
-
assert "
|
|
588
|
+
assert "1f58376d-7947-9826" in capsys.readouterr().out
|
|
589
589
|
output.configure(no_color=True, quiet=False)
|
|
590
590
|
|
|
591
591
|
|
|
@@ -659,7 +659,7 @@ def test_render_users_box_and_footer(capsys):
|
|
|
659
659
|
output.users_footer(users)
|
|
660
660
|
cap = capsys.readouterr()
|
|
661
661
|
out, err = cap.out, cap.err
|
|
662
|
-
assert "
|
|
662
|
+
assert "USERS · 3" in out
|
|
663
663
|
for c in ("email", "access", "perms", "joined", "status"):
|
|
664
664
|
assert c in out
|
|
665
665
|
assert "root@test" in out and "admin" in out and "06-25" in out
|
|
@@ -695,7 +695,7 @@ def test_render_user_show_card_and_perms(capsys):
|
|
|
695
695
|
out = capsys.readouterr().out # show is a read view → stdout
|
|
696
696
|
assert "user" in out and "dev@test" in out and "protected" in out
|
|
697
697
|
assert "access admin" in out and "3 permissions" in out and "active" in out
|
|
698
|
-
assert "
|
|
698
|
+
assert "PERMISSIONS · 3" in out and "keys" in out and "dashboards" in out
|
|
699
699
|
output.configure(no_color=True, quiet=False)
|
|
700
700
|
|
|
701
701
|
|
|
@@ -706,7 +706,7 @@ def test_render_user_created_green_card(capsys):
|
|
|
706
706
|
err = capsys.readouterr().err # write result → stderr chrome
|
|
707
707
|
assert "user created" in err and "new@test" in err
|
|
708
708
|
assert "access read-only" in err and "2 permissions" in err and "active" in err
|
|
709
|
-
assert "
|
|
709
|
+
assert "PERMISSIONS · 2 · read-only" in err # suffix shows the set
|
|
710
710
|
output.configure(no_color=True, quiet=False)
|
|
711
711
|
|
|
712
712
|
|
|
@@ -718,7 +718,7 @@ def test_render_user_updated_diff(capsys):
|
|
|
718
718
|
err = capsys.readouterr().err
|
|
719
719
|
assert "permissions updated · dev@test" in err
|
|
720
720
|
assert "now 3" in err and "+1 added" in err and "−1 removed" in err
|
|
721
|
-
assert "
|
|
721
|
+
assert "PERMISSIONS · 3" in err # NEW set size (struck removal not counted)
|
|
722
722
|
assert "+create" in err and "-write" in err # added chip / removed ghost (no-color prefixes)
|
|
723
723
|
assert "unchanged" in err # legend
|
|
724
724
|
output.configure(no_color=True, quiet=False)
|
|
@@ -1146,7 +1146,7 @@ def test_render_settings_box(capsys):
|
|
|
1146
1146
|
output.render_settings(rows)
|
|
1147
1147
|
cap = capsys.readouterr()
|
|
1148
1148
|
out, err = cap.out, cap.err
|
|
1149
|
-
assert "
|
|
1149
|
+
assert "SETTINGS · 3" in out
|
|
1150
1150
|
for c in ("key", "value", "type", "updated"):
|
|
1151
1151
|
assert c in out
|
|
1152
1152
|
assert "session_ttl_secs" in out and "86400" in out and "integer" in out
|
|
@@ -1231,10 +1231,10 @@ def test_render_incidents_box_and_footer(capsys):
|
|
|
1231
1231
|
output.incidents_footer(incs)
|
|
1232
1232
|
cap = capsys.readouterr()
|
|
1233
1233
|
out, err = cap.out, cap.err
|
|
1234
|
-
assert "
|
|
1234
|
+
assert "ISSUES · 3" in out
|
|
1235
1235
|
for c in ("id", "alert", "severity", "state", "opened", "assignees"):
|
|
1236
1236
|
assert c in out
|
|
1237
|
-
assert "
|
|
1237
|
+
assert "1f5803aaaaaabbbbcccc000000009826" in out
|
|
1238
1238
|
assert "—" in out # manual incident (no alert_name) + no assignees
|
|
1239
1239
|
assert "firing" in out and "acknowledged" in out and "resolved" in out
|
|
1240
1240
|
assert "3 issues" in err and "1 firing" in err and "1 resolved" in err # footer distribution
|
|
@@ -1271,7 +1271,7 @@ def test_render_incident_show_sections(capsys):
|
|
|
1271
1271
|
)
|
|
1272
1272
|
output.render_incident_show(inc)
|
|
1273
1273
|
out = capsys.readouterr().out
|
|
1274
|
-
assert "p95 latency" in out and "
|
|
1274
|
+
assert "p95 latency" in out and "1f5803aaaaaabbbbcccc000000009826" in out
|
|
1275
1275
|
assert "acknowledged by ops@example.com" in out and "assigned to a@example.com" in out
|
|
1276
1276
|
assert "breach" in out and "1240ms" in out
|
|
1277
1277
|
assert "comments · 2" in out and "on it" in out and "(deleted)" in out
|
|
@@ -1357,7 +1357,7 @@ def test_confirm_incident_resolve_headline(capsys, monkeypatch):
|
|
|
1357
1357
|
monkeypatch.setattr(output.typer, "confirm", lambda *a, **k: False)
|
|
1358
1358
|
assert output.confirm_incident_resolve("1f5803aaaaaabbbbcccc000000009826", "p95 latency") is False
|
|
1359
1359
|
err = capsys.readouterr().err
|
|
1360
|
-
assert "resolve issue" in err and "
|
|
1360
|
+
assert "resolve issue" in err and "1f5803aaaaaabbbbcccc000000009826" in err and "(p95 latency)" in err and "this closes it" in err
|
|
1361
1361
|
|
|
1362
1362
|
|
|
1363
1363
|
def test_incident_plain_feedback_lines(capsys):
|
|
@@ -1374,14 +1374,14 @@ def test_incident_plain_feedback_lines(capsys):
|
|
|
1374
1374
|
output.incident_comment_not_found("c0ffee001111")
|
|
1375
1375
|
output.incident_failed("a@x.com is not an operator")
|
|
1376
1376
|
err = capsys.readouterr().err
|
|
1377
|
-
assert "acknowledged issue
|
|
1378
|
-
assert "resolved issue
|
|
1379
|
-
assert "assigned
|
|
1380
|
-
assert "cleared assignees on
|
|
1377
|
+
assert f"acknowledged issue {iid}" in err
|
|
1378
|
+
assert f"resolved issue {iid}" in err
|
|
1379
|
+
assert f"assigned {iid}" in err and "a@example.com, b@example.com" in err
|
|
1380
|
+
assert f"cleared assignees on {iid}" in err
|
|
1381
1381
|
assert "subscribed you to issue" in err and "unsubscribed x@y.z from issue" in err
|
|
1382
1382
|
assert "deleted comment" in err
|
|
1383
|
-
assert "no issue
|
|
1384
|
-
assert "no comment" in err and "
|
|
1383
|
+
assert f"no issue {iid}" in err and "fp issues list" in err
|
|
1384
|
+
assert "no comment" in err and "c0ffee001111" in err
|
|
1385
1385
|
assert "a@x.com is not an operator" in err
|
|
1386
1386
|
|
|
1387
1387
|
|
|
@@ -1399,7 +1399,7 @@ def test_render_agent_health_configured(capsys):
|
|
|
1399
1399
|
_wide_stdout()
|
|
1400
1400
|
output.render_agent_health(configured=True, default_model="claude-x", model_count=3)
|
|
1401
1401
|
out = capsys.readouterr().out # data view → stdout
|
|
1402
|
-
assert "
|
|
1402
|
+
assert "ASSISTANT" in out and "configured" in out
|
|
1403
1403
|
assert "default model claude-x" in out and "3 models available" in out
|
|
1404
1404
|
output.configure(no_color=True, quiet=False)
|
|
1405
1405
|
|
|
@@ -1417,7 +1417,7 @@ def test_render_agent_models_marks_default(capsys):
|
|
|
1417
1417
|
_wide_stdout()
|
|
1418
1418
|
output.render_agent_models(["m-default", "m-fast"], default_model="m-default")
|
|
1419
1419
|
out = capsys.readouterr().out
|
|
1420
|
-
assert "
|
|
1420
|
+
assert "MODELS · 2" in out and "m-default" in out and "m-fast" in out and "default" in out
|
|
1421
1421
|
output.configure(no_color=True, quiet=False)
|
|
1422
1422
|
|
|
1423
1423
|
|
|
@@ -1425,7 +1425,7 @@ def test_render_agent_models_empty(capsys):
|
|
|
1425
1425
|
_wide_stdout()
|
|
1426
1426
|
output.render_agent_models([], default_model=None)
|
|
1427
1427
|
out = capsys.readouterr().out
|
|
1428
|
-
assert "
|
|
1428
|
+
assert "MODELS · 0" in out and "no models reported" in out
|
|
1429
1429
|
output.configure(no_color=True, quiet=False)
|
|
1430
1430
|
|
|
1431
1431
|
|
|
@@ -1438,12 +1438,12 @@ def test_render_agent_chats_box(capsys):
|
|
|
1438
1438
|
output.render_agent_chats(chats)
|
|
1439
1439
|
cap = capsys.readouterr()
|
|
1440
1440
|
out, err = cap.out, cap.err
|
|
1441
|
-
assert "
|
|
1441
|
+
assert "CHATS · 2" in out
|
|
1442
1442
|
for c in ("chat-id", "title", "messages", "updated"):
|
|
1443
1443
|
assert c in out
|
|
1444
1444
|
assert "older" in out and "newer" in out
|
|
1445
1445
|
assert "07854990" in out and "17bf35c3" in out # short copy-friendly handle (first 8)
|
|
1446
|
-
assert "dade"
|
|
1446
|
+
assert "07854990-dade-4dea-aaaa" in out
|
|
1447
1447
|
assert out.index("newer") < out.index("older") # newest first
|
|
1448
1448
|
assert "open one with" not in err # the footer hint was removed
|
|
1449
1449
|
output.configure(no_color=True, quiet=False)
|
|
@@ -1492,7 +1492,7 @@ def test_render_agent_delete_preview_and_feedback(capsys):
|
|
|
1492
1492
|
output.print_cancelled("nothing deleted") # boxed cancel, like the command uses
|
|
1493
1493
|
err = capsys.readouterr().err
|
|
1494
1494
|
assert "delete chat" in err and "perf review" in err and "4 messages" in err
|
|
1495
|
-
assert "07854990
|
|
1495
|
+
assert "07854990-dade" in err # full id in the preview
|
|
1496
1496
|
assert "deleted chat" in err and "perf review" in err # boxed deleted
|
|
1497
1497
|
assert "cancelled" in err and "nothing deleted" in err # boxed cancel
|
|
1498
1498
|
output.configure(no_color=True, quiet=False)
|
|
@@ -1517,7 +1517,7 @@ def test_agent_ask_chrome_lines(capsys):
|
|
|
1517
1517
|
output.agent_unconfigured_note()
|
|
1518
1518
|
err = capsys.readouterr().err
|
|
1519
1519
|
assert "used tool: run_query" in err
|
|
1520
|
-
assert "new chat" in err and 'fp agent ask --chat 07854990 "…"' in err
|
|
1520
|
+
assert "new chat" in err and 'fp agent ask --chat 07854990-dade-4dea "…"' in err
|
|
1521
1521
|
assert "assistant error: boom" in err
|
|
1522
1522
|
assert "chat not found" in err and "fp agent chats" in err # boxed not-found
|
|
1523
1523
|
assert "isn't configured" in err and "fp agent health" in err
|
|
@@ -1642,7 +1642,7 @@ def test_policy_list_counts_policies_and_captions_versions(capsys):
|
|
|
1642
1642
|
_wide_stdout()
|
|
1643
1643
|
output.render_policies([pv("a", 1), pv("b", 2), pv("b", 1)])
|
|
1644
1644
|
out = capsys.readouterr().out
|
|
1645
|
-
assert "
|
|
1645
|
+
assert "POLICIES · 2 · 3 versions" in out
|
|
1646
1646
|
# newest version of each policy first, rather than server order
|
|
1647
1647
|
b_rows = [ln for ln in out.splitlines() if "b" in ln and "v" in ln
|
|
1648
1648
|
and ("v1" in ln or "v2" in ln)]
|
|
@@ -1657,7 +1657,7 @@ def test_policy_list_omits_the_caption_when_each_policy_has_one_version(capsys):
|
|
|
1657
1657
|
source=None, created_at="", created_by=None,
|
|
1658
1658
|
disabled=False, archived=False)])
|
|
1659
1659
|
out = capsys.readouterr().out
|
|
1660
|
-
assert "
|
|
1660
|
+
assert "POLICIES · 1" in out and "versions" not in out
|
|
1661
1661
|
|
|
1662
1662
|
|
|
1663
1663
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from io import StringIO
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import respx
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
|
|
8
|
+
from fp_cli import output
|
|
9
|
+
from fp_cli.app import app
|
|
10
|
+
|
|
11
|
+
BASE = "http://dash.test"
|
|
12
|
+
SOURCE = 'EvalResult(score=Score(0.7), reasoning="Tool usage")'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize("command", ["create", "deploy"])
|
|
16
|
+
@pytest.mark.parametrize("force", [False, True])
|
|
17
|
+
@pytest.mark.parametrize("json_mode", [False, True])
|
|
18
|
+
@pytest.mark.parametrize("width", [80, 140])
|
|
19
|
+
def test_failed_preview_stops_unless_force(logged_in, runner, tmp_path, monkeypatch, command, force, json_mode, width):
|
|
20
|
+
monkeypatch.setenv("COLUMNS", str(width))
|
|
21
|
+
file = tmp_path / "source.py"
|
|
22
|
+
file.write_text(SOURCE)
|
|
23
|
+
old = {"id": "old", "eval_key": "tool_usage", "eval_version": "1", "enabled": True}
|
|
24
|
+
preview = {
|
|
25
|
+
"runs": [
|
|
26
|
+
{"session_id": "run-check", "ok": True,
|
|
27
|
+
"result": {"score": {"value": 0.7}, "reasoning": "Seven tool calls"}},
|
|
28
|
+
{"session_id": "failed-check", "ok": False, "error_kind": "invalid_result",
|
|
29
|
+
"error_message": "Missing result score"},
|
|
30
|
+
],
|
|
31
|
+
"summary": {"succeeded": 1, "failed": 1, "skipped": 0},
|
|
32
|
+
}
|
|
33
|
+
capture = StringIO()
|
|
34
|
+
original_configure = output.configure
|
|
35
|
+
|
|
36
|
+
def configure_capture(**kwargs):
|
|
37
|
+
original_configure(**kwargs)
|
|
38
|
+
if not json_mode:
|
|
39
|
+
output._stdout = Console(file=capture, width=width, no_color=True)
|
|
40
|
+
|
|
41
|
+
monkeypatch.setattr(output, "configure", configure_capture)
|
|
42
|
+
with respx.mock(assert_all_called=False) as mock:
|
|
43
|
+
mock.get(BASE + "/api/evaluator-definitions").respond(200, json=[old])
|
|
44
|
+
mock.post(BASE + "/api/evaluator-definitions/dry-run").respond(200, json=preview)
|
|
45
|
+
created = {
|
|
46
|
+
"id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True,
|
|
47
|
+
}
|
|
48
|
+
create = mock.post(BASE + "/api/evaluator-definitions")
|
|
49
|
+
patch = mock.patch(BASE + "/api/evaluator-definitions/old").respond(200, json={})
|
|
50
|
+
|
|
51
|
+
def preview_happened_first(request):
|
|
52
|
+
# Data must already be visible to the user when creation starts.
|
|
53
|
+
if not json_mode:
|
|
54
|
+
assert "Missing result score" in capture.getvalue()
|
|
55
|
+
assert "Seven tool calls" in capture.getvalue()
|
|
56
|
+
import httpx
|
|
57
|
+
return httpx.Response(201, json=created)
|
|
58
|
+
|
|
59
|
+
create.mock(side_effect=preview_happened_first)
|
|
60
|
+
flags = ["--key", "tool_usage", "--name", "Tool usage"] if command == "create" else ["tool_usage"]
|
|
61
|
+
result = runner.invoke(app, [
|
|
62
|
+
*(["--json"] if json_mode else []), "evaluators", command, *flags,
|
|
63
|
+
"--code", str(file), "--dry-run-on", "run-check", "--dry-run-on", "failed-check",
|
|
64
|
+
*(["--force"] if force else []),
|
|
65
|
+
])
|
|
66
|
+
assert result.exit_code == (0 if force else 1), result.output
|
|
67
|
+
assert create.called is force
|
|
68
|
+
assert patch.called is (force and command == "deploy")
|
|
69
|
+
if json_mode:
|
|
70
|
+
payload = json.loads(result.stdout)
|
|
71
|
+
if force:
|
|
72
|
+
assert payload == (
|
|
73
|
+
{"created": {"id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True}, "disabled": ["old"]}
|
|
74
|
+
if command == "deploy" else {"id": "new", "eval_key": "tool_usage", "eval_version": "2", "enabled": True}
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
assert payload["exit_code"] == 1
|
|
78
|
+
assert "Missing result score" in payload["error"]
|
|
79
|
+
else:
|
|
80
|
+
rendered = capture.getvalue()
|
|
81
|
+
assert "failed-check · failed" in rendered
|
|
82
|
+
assert "Missing result score" in rendered
|
|
83
|
+
if force:
|
|
84
|
+
assert "--force" in result.stderr
|
|
85
|
+
assert rendered.index("Missing result score") < rendered.index(
|
|
86
|
+
"deployed tool_usage" if command == "deploy" else "EVALUATOR CREATED")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.mark.parametrize("width", [80, 140])
|
|
90
|
+
def test_preview_skip_has_deliberate_outcome(logged_in, runner, tmp_path, monkeypatch, width):
|
|
91
|
+
monkeypatch.setenv("COLUMNS", str(width))
|
|
92
|
+
file = tmp_path / "source.py"
|
|
93
|
+
file.write_text(SOURCE)
|
|
94
|
+
with respx.mock as mock:
|
|
95
|
+
mock.post(BASE + "/api/evaluator-definitions/dry-run").respond(200, json={
|
|
96
|
+
"runs": [{"session_id": "run-check", "ok": True, "applicable": False, "result": None}],
|
|
97
|
+
"summary": {"succeeded": 0, "failed": 0, "skipped": 1},
|
|
98
|
+
})
|
|
99
|
+
result = runner.invoke(app, ["evaluators", "dry-run", "--code", str(file), "--session", "run-check"])
|
|
100
|
+
assert result.exit_code == 0, result.output
|
|
101
|
+
assert "○ run-check · skipped" in result.stdout
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
105
|
+
def test_deploy_file_without_key(logged_in, runner, tmp_path, key_mode):
|
|
106
|
+
file = tmp_path / "spec.json"
|
|
107
|
+
file.write_text(json.dumps({"display_name": "Tool usage", "evaluator_source": SOURCE, "eval_version": "2"}))
|
|
108
|
+
prefix = "/v1" if key_mode else "/api"
|
|
109
|
+
with respx.mock as mock:
|
|
110
|
+
mock.get(BASE + prefix + "/evaluator-definitions").respond(200, json=[])
|
|
111
|
+
create = mock.post(BASE + prefix + "/evaluator-definitions").respond(201, json={"id": "new"})
|
|
112
|
+
result = runner.invoke(app, [
|
|
113
|
+
"--json", *(["--api-key", "key"] if key_mode else []),
|
|
114
|
+
"evaluators", "deploy", "tool_usage", "--file", str(file),
|
|
115
|
+
])
|
|
116
|
+
assert result.exit_code == 0, result.output
|
|
117
|
+
assert json.loads(create.calls[0].request.content)["eval_key"] == "tool_usage"
|
|
118
|
+
assert json.loads(result.stdout) == {"created": {"id": "new"}, "disabled": []}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.parametrize("command", [["plan"], ["plan", "claim", "team_offer"]])
|
|
122
|
+
@pytest.mark.parametrize("json_mode", [False, True])
|
|
123
|
+
def test_plan_needs_signed_in_person_before_http(logged_in, runner, command, json_mode):
|
|
124
|
+
with respx.mock as mock:
|
|
125
|
+
result = runner.invoke(app, ["--api-key", "key", *(["--json"] if json_mode else []), *command])
|
|
126
|
+
assert not mock.calls
|
|
127
|
+
assert result.exit_code == 2
|
|
128
|
+
expected = f"`fp {' '.join(command[:2])}` needs a signed-in session: run `fp login`"
|
|
129
|
+
assert expected in result.output
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
133
|
+
@pytest.mark.parametrize("body,expected", [
|
|
134
|
+
({}, "not available on this FailproofAI Cloud yet"),
|
|
135
|
+
({"error": "unknown route"}, "not available on this FailproofAI Cloud yet"),
|
|
136
|
+
({"error": "setting not found"}, "setting not found"),
|
|
137
|
+
])
|
|
138
|
+
def test_setting_key_404_on_older_cloud(logged_in, runner, key_mode, body, expected):
|
|
139
|
+
prefix = "/v1" if key_mode else "/api"
|
|
140
|
+
with respx.mock as mock:
|
|
141
|
+
mock.put(BASE + prefix + "/settings/otel.ingest_enabled").respond(404, json=body)
|
|
142
|
+
result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), "otel", "enable"])
|
|
143
|
+
assert result.exit_code == 6, result.output
|
|
144
|
+
assert json.loads(result.stdout)["error"] == expected
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from io import StringIO
|
|
3
|
+
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from fp_cli import output, theme
|
|
7
|
+
from fp_cli.models import AgentEvent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_two_accent_palette():
|
|
11
|
+
assert theme.ACCENT == "#66d1b5"
|
|
12
|
+
assert theme.PINK == "#e4587d"
|
|
13
|
+
assert {theme.SUCCESS, theme.ERROR, theme.AMBER, theme.BLUE, theme.SCORE_HIGH} <= {theme.ACCENT, theme.PINK}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_permission_risk_order_is_independent_of_shared_colours():
|
|
17
|
+
assert theme.PERM_WRITE == theme.PERM_ACTION == theme.PERM_DANGER
|
|
18
|
+
assert sorted(["delete", "run", "write", "read"], key=output._perm_act_key) == [
|
|
19
|
+
"read", "write", "run", "delete",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_open_table_and_header(capsys):
|
|
24
|
+
output.configure(no_color=True)
|
|
25
|
+
output.print_table(["Id", "URL"], [["run-001", "https://cloud.example.test"]], title="Resources")
|
|
26
|
+
text = capsys.readouterr().out
|
|
27
|
+
assert "failproof ai v" in text
|
|
28
|
+
assert "RESOURCES" in text
|
|
29
|
+
assert not any(c in text for c in "╭╮╰╯┌┐└┘─━│┃")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_legacy_panels_use_open_screen(capsys):
|
|
33
|
+
output.configure(no_color=True)
|
|
34
|
+
output.render_events([AgentEvent(id=1, session_id="run-001", agent_id="agent", event_type="tool_use", ts="2026-10-13T00:00:00Z")])
|
|
35
|
+
text = capsys.readouterr().out
|
|
36
|
+
assert "EVENTS" in text
|
|
37
|
+
assert "failproof ai v" in text
|
|
38
|
+
assert not any(c in text for c in "╭╮╰╯┌┐└┘─━│┃")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_narrow_ids_and_urls_are_not_cut(capsys):
|
|
42
|
+
output.configure(no_color=True)
|
|
43
|
+
output._stdout = Console(width=45, no_color=True)
|
|
44
|
+
id = "session-001-abcdefghijklmnopqrstuvwxyz-123456789"
|
|
45
|
+
url = "https://cloud.example.test/organizations/reliability/sessions/" + id
|
|
46
|
+
output.print_table(["Id", "URL"], [[id, url]])
|
|
47
|
+
compact = "".join(capsys.readouterr().out.split())
|
|
48
|
+
assert id in compact and url in compact
|
|
49
|
+
assert "…" not in compact
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_json_bytes_unchanged(capsys):
|
|
53
|
+
output.configure(json=True)
|
|
54
|
+
data = {"session_id": "run-001", "scores": {"quality": 0.9}}
|
|
55
|
+
output.emit_json(data)
|
|
56
|
+
assert capsys.readouterr().out == json.dumps(data, indent=2, ensure_ascii=False) + "\n"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_generated_help_has_no_boxes(logged_in, runner):
|
|
60
|
+
from fp_cli.app import app
|
|
61
|
+
result = runner.invoke(app, ["evaluators", "create", "--help"])
|
|
62
|
+
assert result.exit_code == 0
|
|
63
|
+
assert "Code:" in result.stdout and "Judge:" in result.stdout and "Jev:" in result.stdout
|
|
64
|
+
assert not any(c in result.stdout for c in "╭╮╰╯┌┐└┘─━│┃")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_shared_key_value_labels_are_grey():
|
|
68
|
+
capture = StringIO()
|
|
69
|
+
output.configure()
|
|
70
|
+
output._stdout = Console(file=capture, width=140, force_terminal=True, color_system="truecolor")
|
|
71
|
+
output.print_table(["Field", "Value"], [["enabled", "true"]])
|
|
72
|
+
assert "\x1b[38;2;118;127;139menabled" in capture.getvalue()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_help_typeable_commands_are_pink():
|
|
76
|
+
capture = StringIO()
|
|
77
|
+
output.configure()
|
|
78
|
+
output._stdout = Console(file=capture, width=140, force_terminal=True, color_system="truecolor")
|
|
79
|
+
output.render_top_level_help()
|
|
80
|
+
assert "\x1b[38;2;228;88;125mfp login" in capture.getvalue()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import respx
|
|
3
|
+
|
|
4
|
+
from fp_cli.app import app
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
8
|
+
@pytest.mark.parametrize("status", [200, 404])
|
|
9
|
+
def test_re_evaluate(logged_in, runner, key_mode, status):
|
|
10
|
+
prefix = "v1" if key_mode else "api"
|
|
11
|
+
with respx.mock:
|
|
12
|
+
route = respx.post(f"http://dash.test/{prefix}/sessions/run-001/re-evaluate").respond(
|
|
13
|
+
status, json={"queued": True} if status == 200 else {"error": "unknown route"},
|
|
14
|
+
)
|
|
15
|
+
result = runner.invoke(app, ["--json", *(["--api-key", "key"] if key_mode else []), "sessions", "re-evaluate", "run-001"])
|
|
16
|
+
assert route.called
|
|
17
|
+
assert result.exit_code == (0 if status == 200 else 6), result.output
|
|
18
|
+
if status == 404:
|
|
19
|
+
assert "not available on this FailproofAI Cloud yet" in result.stdout
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.parametrize("key_mode", [False, True])
|
|
23
|
+
@pytest.mark.parametrize("error_body", [{}, {"error": "session not found"}])
|
|
24
|
+
def test_re_evaluate_missing_session(logged_in, runner, key_mode, error_body):
|
|
25
|
+
prefix = "v1" if key_mode else "api"
|
|
26
|
+
with respx.mock as mock:
|
|
27
|
+
mock.post(f"http://dash.test/{prefix}/sessions/missing/re-evaluate").respond(404, json=error_body)
|
|
28
|
+
result = runner.invoke(app, [
|
|
29
|
+
"--json", *(["--api-key", "key"] if key_mode else []), "sessions", "re-evaluate", "missing",
|
|
30
|
+
])
|
|
31
|
+
assert result.exit_code == 6, result.output
|
|
32
|
+
assert "not found" in result.stdout
|
|
33
|
+
assert "not available on this FailproofAI Cloud yet" not in result.stdout
|