@bongos/core 1.19.1073 → 1.19.1075
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/.bongos-core.json +404 -549
- package/.claude/skills/feedback/SKILL.md +2 -2
- package/bin/bongos.js +1 -3
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +4 -60
- package/clients/bongos-client/index.cjs +4 -60
- package/clients/bongos-client/index.d.ts +5 -90
- package/clients/bongos-client/index.mjs +4 -60
- package/config/branding.neutral.json +1 -5
- package/config/modules.neutral.json +2 -3
- package/config/scheduled-routines.json +0 -2
- package/docs/adr/0145-free-hosted-project-tier-isolation-and-domain-separation.md +1 -0
- package/docs/adr/0285-a-shared-box-holds-about-twelve-projects-per-gb-and-memory-is-the-wall.md +1 -0
- package/docs/adr/0323-hosting-is-three-shapes-and-we-are-not-the-landlord.md +1 -1
- package/docs/adr/0327-a-cloud-host-runs-on-the-owners-account-and-the-key-is-borrowed.md +1 -1
- package/docs/adr/0341-the-page-is-the-unit-of-tweak-mode.md +2 -0
- package/docs/adr/0345-we-host-every-projects-hall-and-its-app-deploys-where-the-owner-chooses.md +94 -0
- package/docs/adr/README.md +1 -0
- package/docs/api/openapi.json +175 -1665
- package/docs/api-reference.md +9 -41
- package/docs/architecture.md +16 -1
- package/docs/branding-contract.md +0 -3
- package/docs/copy-inventory.md +454 -479
- package/docs/copy-registry.json +746 -978
- package/docs/file-map.md +5 -34
- package/docs/module-api-changelog.md +4 -0
- package/docs/modules-contract.md +13 -1
- package/docs/onboarding/diagrams/04-architecture.mmd +11 -22
- package/docs/onboarding/diagrams/README.md +3 -3
- package/docs/onboarding/diagrams/assertions.json +2 -22
- package/docs/onboarding/primer.md +9 -28
- package/docs/page-inventory.json +4 -1
- package/docs/page-readings.json +1032 -1077
- package/docs/recipes/render-pilot.md +29 -10
- package/migrations/core_257_module_store_registry.sql +87 -0
- package/migrations/core_258_drop_dev_box_tables.sql +55 -0
- package/modules/agents/module.json +1 -0
- package/modules/autonomy/module.json +1 -0
- package/modules/builder-settings/module.json +1 -0
- package/modules/builder-settings/render-prefs.js +4 -5
- package/modules/copy-desk/docx.js +78 -5
- package/modules/copy-desk/module.json +1 -0
- package/modules/copy-desk/routes/copy-desk.js +10 -10
- package/modules/copy-desk/tests/copy_docx.mjs +107 -1
- package/modules/copy-desk/tests/fixtures/word-docx.mjs +30 -0
- package/modules/discord/module.json +1 -0
- package/modules/discord/ship-broadcast.js +3 -3
- package/modules/economy/credits.js +2 -2
- package/modules/economy/module.json +1 -0
- package/modules/economy/routes/credits.js +1 -2
- package/modules/government/catalog.js +13 -17
- package/modules/government/migrations/government_019_retire_box_permissions.sql +33 -0
- package/modules/government/module.json +1 -0
- package/modules/government/protected-surfaces.json +3 -11
- package/modules/government/resolver.js +3 -46
- package/modules/government/routes/government.js +0 -2
- package/modules/government/session-scopes.js +43 -83
- package/modules/government/session-scopes.json +8 -18
- package/modules/grading/module.json +1 -0
- package/modules/hall-ui/module.json +1 -0
- package/modules/hall-ui/public/board-lib.js +2 -2
- package/modules/hall-ui/public/builders.js +29 -40
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/collab.js +1 -1
- package/modules/hall-ui/public/diagrams.html +3 -3
- package/modules/hall-ui/public/dom-utils.js +1 -1
- package/modules/hall-ui/public/gate.js +2 -2
- package/modules/hall-ui/public/gate.states.json +1 -1
- package/modules/hall-ui/public/hall-render.js +43 -79
- package/modules/hall-ui/public/index.html +5 -1
- package/modules/hall-ui/public/modules.js +1 -0
- package/modules/hall-ui/public/oversight.css +5 -5
- package/modules/hall-ui/public/palette.js +0 -11
- package/modules/hall-ui/public/primer.js +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings-sessions.js +1 -1
- package/modules/hall-ui/public/settings.css +0 -1
- package/modules/hall-ui/public/settings.html +4 -33
- package/modules/hall-ui/public/settings.js +3 -33
- package/modules/hall-ui/public/settings.states.json +2 -2
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +10 -13
- package/modules/hall-ui/public/task.html +4 -1
- package/modules/hall-ui/public/task.js +36 -1
- package/modules/hall-ui/public/tweak-editor-lib.js +18 -1
- package/modules/hall-ui/public/tweak-editor.css +16 -0
- package/modules/hall-ui/public/tweak-editor.html +26 -3
- package/modules/hall-ui/public/tweak-editor.js +32 -1
- package/modules/hall-ui/public/work.js +1 -1
- package/modules/hall-ui/records/tweak-editor.md +1 -0
- package/modules/ideas/module.json +1 -0
- package/modules/ideas/projection.js +1 -1
- package/modules/lifecycle/db-claim-reads.js +1 -58
- package/modules/lifecycle/db-ship.js +4 -9
- package/modules/lifecycle/db.js +0 -4
- package/modules/lifecycle/github-push.js +12 -14
- package/modules/lifecycle/kickoff-checklist.js +0 -4
- package/modules/lifecycle/lifecycle.js +0 -8
- package/modules/lifecycle/module.json +1 -0
- package/modules/lifecycle/publish-reconciler.js +10 -10
- package/modules/lifecycle/routes/tasks.js +13 -15
- package/modules/lifecycle/ship-card.js +1 -1
- package/modules/lifecycle/task-visuals.js +2 -2
- package/modules/memory/module.json +1 -0
- package/modules/memory/routes/memory.js +2 -2
- package/modules/npm-release/module.json +1 -0
- package/modules/onboarding/module.json +1 -0
- package/modules/onboarding/onboarding-state.js +17 -36
- package/modules/onboarding/routes/access-requests.js +3 -3
- package/modules/platform-identity/module.json +1 -0
- package/modules/platform-identity/platform-identity.js +13 -5
- package/modules/platform-identity/routes/sso.js +5 -4
- package/modules/platform-identity/tests/platform-identity.mjs +17 -3
- package/modules/provisioning/capacity.js +1 -1
- package/modules/provisioning/module.json +1 -0
- package/modules/provisioning/provisioning.js +6 -5
- package/modules/provisioning/routes/provisioning.js +4 -4
- package/modules/provisioning/starter-bundles.js +5 -25
- package/modules/public-landing/module.json +1 -0
- package/modules/public-landing/public/assets/cosmos.css +1 -1
- package/modules/public-landing/public/contact.html +1 -1
- package/modules/public-landing/public/privacy.html +1 -1
- package/modules/public-landing/public/projects.html +101 -32
- package/modules/public-landing/public/projects.probes.json +2 -2
- package/modules/public-landing/public/projects.states.json +4 -3
- package/modules/public-landing/public/terms.html +1 -1
- package/modules/security/module.json +1 -0
- package/modules/sessions/db.js +1 -1
- package/modules/sessions/module.json +1 -0
- package/modules/specialities/module.json +1 -0
- package/modules/status-ui/module.json +1 -0
- package/modules/ui-design/module.json +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +84 -0
- package/scripts/gds/artifact-format.js +203 -0
- package/scripts/gds/artifact-staleness.js +2 -2
- package/scripts/gds/audit-authorship.sh +1 -13
- package/scripts/gds/backfill-task-rewards.js +5 -5
- package/scripts/gds/claim.js +0 -15
- package/scripts/gds/claude-materialize.js +2 -2
- package/scripts/gds/cli-lib.js +15 -15
- package/scripts/gds/codemod-rename-src-gds.js +1 -1
- package/scripts/gds/context-pack.js +5 -7
- package/scripts/gds/diagram-facts.js +12 -23
- package/scripts/gds/do-api.js +13 -80
- package/scripts/gds/doc-cli-guard.js +2 -0
- package/scripts/gds/doctor.js +4 -4
- package/scripts/gds/feedback-latest.js +139 -9
- package/scripts/gds/fitness-checks-identity.js +2 -7
- package/scripts/gds/fitness-checks-packaging.js +4 -4
- package/scripts/gds/fitness-checks-write-validation.js +4 -0
- package/scripts/gds/fitness-ratchets.js +5 -5
- package/scripts/gds/fitness.js +10 -10
- package/scripts/gds/gen-api-client.js +4 -4
- package/scripts/gds/gen-api-docs.js +1 -1
- package/scripts/gds/gen-diagrams.js +49 -71
- package/scripts/gds/gen-repo-map.js +13 -14
- package/scripts/gds/gen-session-index.js +8 -8
- package/scripts/gds/http-api-client.js +12 -19
- package/scripts/gds/init.js +1 -1
- package/scripts/gds/leak-scan-allowlist.js +1 -1
- package/scripts/gds/lint-infra-exec.js +1 -1
- package/scripts/gds/local-preview-lib.js +8 -39
- package/scripts/gds/local-preview.js +2 -15
- package/scripts/gds/migration-namespace.js +3 -3
- package/scripts/gds/module-artifact.js +212 -0
- package/scripts/gds/module.js +95 -6
- package/scripts/gds/package-core.js +5 -116
- package/scripts/gds/plain-cards.js +1 -1
- package/scripts/gds/provision-config.js +2 -2
- package/scripts/gds/provision-net.js +2 -3
- package/scripts/gds/provision.js +9 -9
- package/scripts/gds/publish-manifest.js +1 -2
- package/scripts/gds/push-path-brief.js +10 -10
- package/scripts/gds/regen-instance-docs.js +1 -1
- package/scripts/gds/rename-history-check.js +1 -1
- package/scripts/gds/run-unit-tests.js +2 -16
- package/scripts/gds/sandbox-stage.js +39 -341
- package/scripts/gds/session-digest-build.js +2 -2
- package/scripts/gds/ship-deploy-target.js +13 -13
- package/scripts/gds/ship-flow.js +7 -7
- package/scripts/gds/ship-land.js +9 -10
- package/scripts/gds/ship-merge.js +4 -4
- package/scripts/gds/ship-preflight-steps.js +4 -4
- package/scripts/gds/ship-regen.js +15 -49
- package/scripts/gds/ship.js +13 -33
- package/scripts/gds/skill-preflight.js +9 -8
- package/scripts/gds/start.js +0 -15
- package/scripts/hall-preview/README.md +5 -5
- package/scripts/hall-preview/server.js +6 -6
- package/scripts/render-diagrams.sh +1 -1
- package/src/bongos/api-errors.js +1 -1
- package/src/bongos/api-prefix.js +4 -4
- package/src/bongos/auth-github.js +2 -3
- package/src/bongos/auth.js +51 -126
- package/src/bongos/db-kernel.js +2 -3
- package/src/bongos/db.js +5 -28
- package/src/bongos/module-scope-map.js +14 -29
- package/src/bongos/module-store.js +141 -0
- package/src/bongos/platform-visibility-gate.js +1 -1
- package/src/bongos/route-rank-check.js +3 -16
- package/src/bongos/routes/auth.js +0 -116
- package/src/bongos/routes/builders.js +5 -15
- package/src/bongos/routes/instance.js +8 -14
- package/src/bongos/routes/me.js +7 -23
- package/src/bongos/routes/modules.js +83 -0
- package/src/bongos/routes/security.js +3 -3
- package/src/bongos/routes.js +7 -2
- package/src/bongos/serve-internal.js +5 -6
- package/src/bongos-downloads.js +1 -6
- package/src/branding.js +2 -2
- package/src/build-info.js +4 -6
- package/src/instance-config.js +2 -2
- package/src/module-api.js +13 -6
- package/src/module-loader/catalog.js +3 -0
- package/src/module-loader/loader.js +4 -4
- package/src/module-loader/manifest-schema.js +16 -8
- package/src/module-seams.js +2 -3
- package/src/modules.js +42 -14
- package/tests/agents_spend_guard.mjs +1 -1
- package/tests/api_alias_caller_ratchet.mjs +1 -1
- package/tests/api_alias_exceptions.mjs +1 -1
- package/tests/api_client.mjs +1 -16
- package/tests/api_path_404.mjs +1 -1
- package/tests/auth_page_gate.mjs +10 -10
- package/tests/backfill_task_rewards.mjs +1 -1
- package/tests/canonical_profile_url.mjs +1 -3
- package/tests/claim_action.mjs +4 -1
- package/tests/claim_from_task_and_home.mjs +127 -0
- package/tests/{box_ship_permissions.mjs → cli_allowlist_permissions.mjs} +5 -5
- package/tests/cli_sessions.mjs +1 -1
- package/tests/consumer_layout_boot.mjs +2 -0
- package/tests/context_pack.mjs +13 -13
- package/tests/copy_desk_page_docx.mjs +33 -2
- package/tests/copy_inventory.mjs +7 -7
- package/tests/core_upgrade_runner.mjs +6 -6
- package/tests/credit_grant.mjs +2 -2
- package/tests/criterion_suggest.mjs +1 -1
- package/tests/deploy_divergence_line.mjs +11 -11
- package/tests/design_tokens_sync.mjs +3 -2
- package/tests/diagram_facts_offset.mjs +1 -1
- package/tests/do_api.mjs +106 -0
- package/tests/feedback_latest.mjs +122 -0
- package/tests/fitness.mjs +44 -42
- package/tests/fitness_ratchets.mjs +2 -2
- package/tests/gate_approvals.mjs +0 -1
- package/tests/github_push_land_proof.mjs +1 -1
- package/tests/go_live.mjs +3 -3
- package/tests/goal_advisory.mjs +3 -3
- package/tests/goal_suggest.mjs +3 -3
- package/tests/government_abuse_matrix.mjs +21 -19
- package/tests/government_ownership_scope.mjs +33 -35
- package/tests/government_parity.mjs +2 -2
- package/tests/government_protected_surfaces.mjs +56 -37
- package/tests/government_require_permission.mjs +3 -3
- package/tests/government_seed.mjs +37 -4
- package/tests/government_session_scope.mjs +92 -366
- package/tests/hall_audit.mjs +1 -1
- package/tests/hall_error_envelope.mjs +4 -0
- package/tests/hall_landing_boot.mjs +4 -1
- package/tests/hall_palette.mjs +1 -16
- package/tests/hall_record_world.mjs +6 -0
- package/tests/hall_tweak_editor.mjs +52 -1
- package/tests/helpers.mjs +1 -1
- package/tests/host_topology_skips.mjs +3 -4
- package/tests/html_comment_nesting.mjs +137 -0
- package/tests/infra_exec_paths.mjs +9 -10
- package/tests/init.mjs +4 -5
- package/tests/instance_manifest.mjs +18 -25
- package/tests/kickoff_checklist.mjs +1 -1
- package/tests/lib_sh_resolution.mjs +0 -221
- package/tests/local_preview.mjs +0 -12
- package/tests/migration_namespace.mjs +6 -6
- package/tests/module-scope-map.mjs +9 -9
- package/tests/module_api.mjs +2 -2
- package/tests/module_catalog.mjs +1 -1
- package/tests/module_cli.mjs +20 -20
- package/tests/module_contributions.mjs +6 -6
- package/tests/module_loader.mjs +6 -6
- package/tests/module_manifest.mjs +26 -17
- package/tests/module_route_rank.mjs +2 -2
- package/tests/module_store_publish.mjs +307 -0
- package/tests/module_store_publish_route.mjs +121 -0
- package/tests/module_store_registry_migration.mjs +70 -0
- package/tests/modules.mjs +56 -26
- package/tests/modules_route_wiring.mjs +1 -1
- package/tests/onboarding_route_signals.mjs +21 -31
- package/tests/onboarding_state.mjs +35 -49
- package/tests/paste_token_session_store.mjs +1 -1
- package/tests/permission_path.mjs +0 -3
- package/tests/platform_boot.mjs +1 -2
- package/tests/platform_visibility_gate.mjs +1 -1
- package/tests/profile_route.mjs +1 -6
- package/tests/project_modules_ui.mjs +17 -17
- package/tests/projects_hub.mjs +5 -5
- package/tests/projects_hub_app_step.mjs +138 -0
- package/tests/projects_hub_module_picker.mjs +77 -76
- package/tests/projects_hub_pre_uat.mjs +4 -5
- package/tests/provision.mjs +7 -7
- package/tests/provisioning_capacity.mjs +1 -1
- package/tests/provisioning_recommendations.mjs +2 -1
- package/tests/provisioning_starter_bundles.mjs +0 -22
- package/tests/publish_branch_route.mjs +11 -19
- package/tests/publish_manifest.mjs +2 -3
- package/tests/publish_reconciler.mjs +8 -8
- package/tests/push_path_brief.mjs +10 -9
- package/tests/rank_tier_single_source.mjs +1 -13
- package/tests/regrade_eligibility.mjs +1 -1
- package/tests/rename_history_restraint.mjs +1 -1
- package/tests/repo_map.mjs +12 -13
- package/tests/runner_drift.mjs +3 -21
- package/tests/sandbox_stage.mjs +57 -390
- package/tests/seam_wiring_guard.mjs +11 -11
- package/tests/search_isolation.mjs +3 -3
- package/tests/session_records.mjs +1 -1
- package/tests/session_rename_fallback.mjs +1 -22
- package/tests/session_start_freshness.mjs +3 -3
- package/tests/session_token_hash_db.mjs +1 -1
- package/tests/ship_card.mjs +1 -1
- package/tests/ship_ci_deploy.mjs +17 -17
- package/tests/ship_error_shape.mjs +1 -1
- package/tests/ship_premerge.mjs +1 -1
- package/tests/ship_resume.mjs +1 -1
- package/tests/skill_preflight.mjs +2 -2
- package/tests/skip_is_not_pass.mjs +1 -1
- package/tests/task_visual_slots.mjs +31 -0
- package/tests/terms_acceptance.mjs +2 -2
- package/tests/upgrade.mjs +4 -4
- package/tests/watch_sealed_floor.mjs +0 -1
- package/tests/wizard_draft_resume.mjs +2 -2
- package/tests/wizard_intent_resume.mjs +21 -10
- package/tests/wizard_preselect_why.mjs +18 -18
- package/docs/onboarding/browser-terminal-guide.md +0 -73
- package/docs/recipes/managed-settings-remote-control.md +0 -416
- package/modules/dev-box/CLAUDE.md +0 -15
- package/modules/dev-box/box-access.js +0 -623
- package/modules/dev-box/box-credential.js +0 -94
- package/modules/dev-box/box-onboard.js +0 -486
- package/modules/dev-box/boxes.js +0 -1038
- package/modules/dev-box/db.js +0 -39
- package/modules/dev-box/module.json +0 -19
- package/modules/dev-box/routes/box.js +0 -1223
- package/scripts/gds/box-auth-check.js +0 -160
- package/scripts/gds/box-infra.js +0 -321
- package/scripts/gds/box-sync.js +0 -216
- package/scripts/gds/box.js +0 -1332
- package/scripts/gds/cf-tunnel.js +0 -558
- package/scripts/gds/smoke-box.sh +0 -150
- package/scripts/gds/tree-preflight.js +0 -241
- package/src/bongos/app-pair.js +0 -172
- package/tests/app_pair.mjs +0 -168
- package/tests/box_access.mjs +0 -578
- package/tests/box_auth_check.mjs +0 -114
- package/tests/box_code_staleness.mjs +0 -129
- package/tests/box_connect_e2e.mjs +0 -196
- package/tests/box_cost.mjs +0 -70
- package/tests/box_credential.mjs +0 -125
- package/tests/box_dns_repoint.mjs +0 -153
- package/tests/box_env.mjs +0 -64
- package/tests/box_host_keys.mjs +0 -126
- package/tests/box_infra_resolve.mjs +0 -246
- package/tests/box_onboard.mjs +0 -317
- package/tests/box_scope_session.mjs +0 -119
- package/tests/box_sweep_docs.mjs +0 -119
- package/tests/box_sync.mjs +0 -55
- package/tests/box_sync_scope_report.mjs +0 -126
- package/tests/box_task_scope.mjs +0 -169
- package/tests/box_task_scope_pause.mjs +0 -66
- package/tests/box_terminal_ssrf.mjs +0 -96
- package/tests/boxes.mjs +0 -1585
- package/tests/cf_ruleset_preflight.mjs +0 -142
- package/tests/cf_tunnel.mjs +0 -496
- package/tests/docs_scrubber_damage.mjs +0 -111
- package/tests/module_scope_active_claims.mjs +0 -94
- package/tests/ship_api_client_pathspec.mjs +0 -100
- package/tests/tree_preflight.mjs +0 -243
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
// tests/cf_ruleset_preflight.mjs — DB-free, network-free unit tests for the
|
|
2
|
-
// Cloudflare RULESET scope preflight (scripts/gds/cf-tunnel.js
|
|
3
|
-
// classifyRulesetScope + checkRulesetScope, task 1002511).
|
|
4
|
-
//
|
|
5
|
-
// WHY ITS OWN FILE: `cf_tunnel` is in run-unit-tests.js's INTEGRATION exclusion
|
|
6
|
-
// set, so anything added only to tests/cf_tunnel.mjs never runs in the default
|
|
7
|
-
// DB-free unit gate that /builder-ship smokes. This file is auto-discovered by
|
|
8
|
-
// that gate, so the preflight's behaviour is actually enforced on every ship.
|
|
9
|
-
//
|
|
10
|
-
// Run: node tests/cf_ruleset_preflight.mjs
|
|
11
|
-
import { test } from 'node:test';
|
|
12
|
-
import assert from 'node:assert/strict';
|
|
13
|
-
import { createRequire } from 'node:module';
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const cf = require('../scripts/gds/cf-tunnel.js');
|
|
16
|
-
|
|
17
|
-
const PHASE = 'http_request_late_transform';
|
|
18
|
-
|
|
19
|
-
// ── the constants the rule + the preflight share ────────────────────────────
|
|
20
|
-
test('the cookie-strip rule constants are exported for callers + this test', () => {
|
|
21
|
-
assert.equal(cf.COOKIE_STRIP_PHASE, PHASE);
|
|
22
|
-
assert.match(cf.COOKIE_STRIP_RULE_DESC, /Cookie/);
|
|
23
|
-
assert.match(cf.COOKIE_STRIP_EXPRESSION, /term-/);
|
|
24
|
-
assert.match(cf.COOKIE_STRIP_EXPRESSION, /sandbox-/);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// ── classifyRulesetScope: PURE, and a NEGATIVE detector only ────────────────
|
|
28
|
-
test('classifyRulesetScope: 403 → NOT readable + guidance naming the zone and Transform Rules', () => {
|
|
29
|
-
const r = cf.classifyRulesetScope({
|
|
30
|
-
status: 403,
|
|
31
|
-
body: { success: false, errors: [{ message: 'Actor does not have permission' }] },
|
|
32
|
-
phase: PHASE,
|
|
33
|
-
zone: 'cloudbongos.com',
|
|
34
|
-
});
|
|
35
|
-
assert.equal(r.readable, false);
|
|
36
|
-
assert.match(r.guidance, /Transform Rules/);
|
|
37
|
-
assert.match(r.guidance, /Cache Rules/);
|
|
38
|
-
assert.match(r.guidance, /cloudbongos\.com/);
|
|
39
|
-
assert.match(r.guidance, /API Tokens/i);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
test('classifyRulesetScope: 401 → NOT readable (definitively no scope)', () => {
|
|
43
|
-
const r = cf.classifyRulesetScope({ status: 401, body: null, phase: PHASE, zone: 'cloudbongos.com' });
|
|
44
|
-
assert.equal(r.readable, false);
|
|
45
|
-
assert.match(r.reason, /401/);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test('classifyRulesetScope: 200 → readable, and NEVER claims edit scope is confirmed', () => {
|
|
49
|
-
const r = cf.classifyRulesetScope({
|
|
50
|
-
status: 200,
|
|
51
|
-
body: { success: true, result: { id: 'rs1', rules: [] } },
|
|
52
|
-
phase: PHASE,
|
|
53
|
-
zone: 'cloudbongos.com',
|
|
54
|
-
});
|
|
55
|
-
assert.equal(r.readable, true);
|
|
56
|
-
// HONESTY GATE: a GET proves READ, not EDIT. There must be no `editable` field,
|
|
57
|
-
// and the human-readable text must not read as a confirmed security control.
|
|
58
|
-
assert.equal('editable' in r, false, 'a read probe must never report an `editable` verdict');
|
|
59
|
-
assert.doesNotMatch(`${r.reason} ${r.guidance || ''}`, /scope confirmed|edit scope (is )?(confirmed|verified|present)/i);
|
|
60
|
-
assert.match(r.reason, /not proven|read/i, 'the reason must say edit scope is unproven');
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test('classifyRulesetScope: 404 entrypoint → readable (an empty phase is legitimate)', () => {
|
|
64
|
-
const r = cf.classifyRulesetScope({
|
|
65
|
-
status: 404,
|
|
66
|
-
body: { success: false, errors: [{ message: 'ruleset not found' }] },
|
|
67
|
-
phase: PHASE,
|
|
68
|
-
zone: 'cloudbongos.com',
|
|
69
|
-
});
|
|
70
|
-
assert.equal(r.readable, true);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
test('classifyRulesetScope: 500 → NOT readable, reported as inconclusive (never a pass)', () => {
|
|
74
|
-
const r = cf.classifyRulesetScope({ status: 500, body: null, phase: PHASE, zone: 'cloudbongos.com' });
|
|
75
|
-
assert.equal(r.readable, false);
|
|
76
|
-
assert.match(r.reason, /inconclusive|failed/i);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
// ── checkRulesetScope: its OWN raw fetch, injectable, never a real call ─────
|
|
80
|
-
test('checkRulesetScope: probes the phase entrypoint (NOT /user/tokens/verify) and reports readable', async () => {
|
|
81
|
-
const calls = [];
|
|
82
|
-
const fetchImpl = async (url, opts = {}) => {
|
|
83
|
-
calls.push({ url, auth: opts.headers.Authorization });
|
|
84
|
-
if (url.includes('/zones?name=')) return { status: 200, json: async () => ({ success: true, result: [{ id: 'zoneA', name: 'cloudbongos.com' }] }) };
|
|
85
|
-
return { status: 200, json: async () => ({ success: true, result: { id: 'rs1', rules: [] } }) };
|
|
86
|
-
};
|
|
87
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', fetchImpl });
|
|
88
|
-
assert.equal(r.readable, true);
|
|
89
|
-
assert.equal(r.zone, 'cloudbongos.com');
|
|
90
|
-
const probe = calls[calls.length - 1];
|
|
91
|
-
assert.match(probe.url, new RegExp(`/zones/zoneA/rulesets/phases/${PHASE}/entrypoint$`));
|
|
92
|
-
assert.equal(probe.auth, 'Bearer tok_1');
|
|
93
|
-
// An IP-locked token returns "Invalid API Token" from verify (builder-box-lifecycle
|
|
94
|
-
// learning) — the preflight must probe the real operation instead.
|
|
95
|
-
assert.ok(!calls.some((c) => /tokens\/verify/.test(c.url)), 'must not use /user/tokens/verify');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test('checkRulesetScope: 403 on the entrypoint → NOT readable + actionable guidance', async () => {
|
|
99
|
-
const fetchImpl = async (url) => {
|
|
100
|
-
if (url.includes('/zones?name=')) return { status: 200, json: async () => ({ success: true, result: [{ id: 'zoneA', name: 'cloudbongos.com' }] }) };
|
|
101
|
-
return { status: 403, json: async () => ({ success: false, errors: [{ message: 'Actor does not have permission' }] }) };
|
|
102
|
-
};
|
|
103
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', fetchImpl });
|
|
104
|
-
assert.equal(r.readable, false);
|
|
105
|
-
assert.match(r.guidance, /Transform Rules/);
|
|
106
|
-
assert.match(r.guidance, /cloudbongos\.com/);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('checkRulesetScope: a known zoneId skips the zone lookup (one call only)', async () => {
|
|
110
|
-
const calls = [];
|
|
111
|
-
const fetchImpl = async (url) => {
|
|
112
|
-
calls.push(url);
|
|
113
|
-
return { status: 200, json: async () => ({ success: true, result: { id: 'rs1', rules: [] } }) };
|
|
114
|
-
};
|
|
115
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok_1', zoneId: 'zoneGiven', fetchImpl });
|
|
116
|
-
assert.equal(r.readable, true);
|
|
117
|
-
assert.equal(calls.length, 1);
|
|
118
|
-
assert.match(calls[0], /\/zones\/zoneGiven\/rulesets\//);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test('checkRulesetScope: no token → NOT readable, and ZERO network calls', async () => {
|
|
122
|
-
let called = false;
|
|
123
|
-
const fetchImpl = async () => { called = true; return { status: 200, json: async () => ({}) }; };
|
|
124
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: '', fetchImpl });
|
|
125
|
-
assert.equal(r.readable, false);
|
|
126
|
-
assert.match(r.reason, /no CLOUDFLARE_API_TOKEN/);
|
|
127
|
-
assert.equal(called, false);
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test('checkRulesetScope: a thrown fetch is caught → NOT readable, never throws', async () => {
|
|
131
|
-
const fetchImpl = async () => { throw new Error('ENETUNREACH'); };
|
|
132
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok', fetchImpl });
|
|
133
|
-
assert.equal(r.readable, false);
|
|
134
|
-
assert.match(r.reason, /failed/i);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
test('checkRulesetScope: the zone lookup rejecting the token → NOT readable', async () => {
|
|
138
|
-
const fetchImpl = async () => ({ status: 403, json: async () => ({ success: false, errors: [{ message: 'Invalid API Token' }] }) });
|
|
139
|
-
const r = await cf.checkRulesetScope({ zone: 'cloudbongos.com', phase: PHASE, token: 'tok', fetchImpl });
|
|
140
|
-
assert.equal(r.readable, false);
|
|
141
|
-
assert.match(r.guidance, /CLOUDFLARE_API_TOKEN/);
|
|
142
|
-
});
|
package/tests/cf_tunnel.mjs
DELETED
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
// tests/cf_tunnel.mjs
|
|
2
|
-
//
|
|
3
|
-
// Hermetic unit smoke for modules/dev-box/cf-tunnel.js — the control-plane Cloudflare
|
|
4
|
-
// client behind the box terminal's NAMED tunnel (#771, ADR 0038 Part B). Mirrors
|
|
5
|
-
// tests/boxes.mjs's coverage of do-api.js: the dry-run/live contract + every
|
|
6
|
-
// request SHAPE asserted against a fake fetch, plus the high-level
|
|
7
|
-
// ensure/teardown composition. NO network, NO account, NO real clock.
|
|
8
|
-
//
|
|
9
|
-
// Run: node tests/cf_tunnel.mjs
|
|
10
|
-
|
|
11
|
-
import { strict as assert } from 'node:assert';
|
|
12
|
-
import { createRequire } from 'node:module';
|
|
13
|
-
|
|
14
|
-
const require = createRequire(import.meta.url);
|
|
15
|
-
const { CfTunnelApi, CfApiError, tunnelName, terminalHostname, previewHostname, GAME_SERVICE, CACHE_BYPASS_RULE_DESC } = require('../scripts/gds/cf-tunnel.js');
|
|
16
|
-
|
|
17
|
-
let passed = 0, failed = 0;
|
|
18
|
-
async function ta(name, fn) {
|
|
19
|
-
try { await fn(); passed++; console.log(` PASS ${name}`); }
|
|
20
|
-
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.stack || e.message}`); }
|
|
21
|
-
}
|
|
22
|
-
function t(name, fn) {
|
|
23
|
-
try { fn(); passed++; console.log(` PASS ${name}`); }
|
|
24
|
-
catch (e) { failed++; console.log(` FAIL ${name}\n ${e.stack || e.message}`); }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const ACCT = 'acct123';
|
|
28
|
-
const TOKEN = 'cf-token';
|
|
29
|
-
|
|
30
|
-
// A fake fetch driven by a routing table keyed on `${METHOD} ${pathWithoutQuery}`.
|
|
31
|
-
// Each handler gets ({ method, url, path, query, body }) and returns either a
|
|
32
|
-
// plain object (wrapped in a 200 CF envelope) or { status, json }. Records calls.
|
|
33
|
-
function makeFetch(routes) {
|
|
34
|
-
const calls = [];
|
|
35
|
-
const fetchImpl = async (url, opts = {}) => {
|
|
36
|
-
const method = opts.method || 'GET';
|
|
37
|
-
const u = new URL(url);
|
|
38
|
-
const path = u.pathname.replace('/client/v4', '');
|
|
39
|
-
const query = Object.fromEntries(u.searchParams.entries());
|
|
40
|
-
const body = opts.body ? JSON.parse(opts.body) : null;
|
|
41
|
-
calls.push({ method, url, path, query, body });
|
|
42
|
-
const key = `${method} ${path}`;
|
|
43
|
-
const handler = routes[key];
|
|
44
|
-
if (!handler) return resp(404, { success: false, errors: [{ message: `no route ${key}` }] });
|
|
45
|
-
const out = handler({ method, url, path, query, body });
|
|
46
|
-
if (out && typeof out.status === 'number') return resp(out.status, out.json);
|
|
47
|
-
return resp(200, { success: true, result: out, errors: [] });
|
|
48
|
-
};
|
|
49
|
-
return { fetchImpl, calls };
|
|
50
|
-
}
|
|
51
|
-
function resp(status, json) {
|
|
52
|
-
return { ok: status >= 200 && status < 300, status, text: async () => JSON.stringify(json) };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
console.log('constructor / dry-run contract:');
|
|
56
|
-
|
|
57
|
-
t('constructor throws live without token', () => {
|
|
58
|
-
assert.throws(() => new CfTunnelApi({ dryRun: false, accountId: ACCT }), CfApiError);
|
|
59
|
-
});
|
|
60
|
-
t('constructor throws live without accountId', () => {
|
|
61
|
-
assert.throws(() => new CfTunnelApi({ dryRun: false, token: TOKEN }), CfApiError);
|
|
62
|
-
});
|
|
63
|
-
t('dry-run is the default (no token needed)', () => {
|
|
64
|
-
const cf = new CfTunnelApi({});
|
|
65
|
-
assert.equal(cf.dryRun, true);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
await ta('live requests pass the IPv4 dispatcher through (token is IP-locked, #771)', async () => {
|
|
69
|
-
let sawDispatcher;
|
|
70
|
-
const fetchImpl = async (_url, opts) => { sawDispatcher = opts.dispatcher; return resp(200, { success: true, result: [] }); };
|
|
71
|
-
const sentinel = { iam: 'ipv4-agent' };
|
|
72
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl, dispatcher: sentinel });
|
|
73
|
-
await cf.findTunnelByName('x');
|
|
74
|
-
assert.equal(sawDispatcher, sentinel, 'the configured dispatcher must reach fetch');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
await ta('dry-run never touches the network and returns synthetic results', async () => {
|
|
78
|
-
let hit = false;
|
|
79
|
-
const cf = new CfTunnelApi({ accountId: ACCT, fetchImpl: () => { hit = true; throw new Error('should not fetch'); } });
|
|
80
|
-
const r = await cf.ensureNamedTunnel({ login: 'alice', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
81
|
-
assert.equal(hit, false, 'fetch must not be called in dry-run');
|
|
82
|
-
assert.equal(r.hostname, 'term-alice.dev.example.com');
|
|
83
|
-
assert.ok(String(r.token).startsWith('dryrun-token-'));
|
|
84
|
-
assert.ok(String(r.tunnelId).startsWith('dryrun-tunnel-'));
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
console.log('tunnel lifecycle (live, fake fetch):');
|
|
88
|
-
|
|
89
|
-
await ta('ensureTunnel reuses an existing tunnel (no POST)', async () => {
|
|
90
|
-
const { fetchImpl, calls } = makeFetch({
|
|
91
|
-
'GET /accounts/acct123/cfd_tunnel': () => [{ id: 'tun-existing', name: 'otb-term-alice' }],
|
|
92
|
-
});
|
|
93
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
94
|
-
const { tunnel, created } = await cf.ensureTunnel('otb-term-alice');
|
|
95
|
-
assert.equal(tunnel.id, 'tun-existing');
|
|
96
|
-
assert.equal(created, false);
|
|
97
|
-
assert.ok(!calls.some((c) => c.method === 'POST'), 'must not create when one exists');
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
await ta('ensureTunnel creates when none exists (config_src=cloudflare)', async () => {
|
|
101
|
-
const { fetchImpl, calls } = makeFetch({
|
|
102
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
103
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tun-new', name: body.name }),
|
|
104
|
-
});
|
|
105
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
106
|
-
const { tunnel, created } = await cf.ensureTunnel('otb-term-bob');
|
|
107
|
-
assert.equal(tunnel.id, 'tun-new');
|
|
108
|
-
assert.equal(created, true);
|
|
109
|
-
const post = calls.find((c) => c.method === 'POST');
|
|
110
|
-
assert.equal(post.body.name, 'otb-term-bob');
|
|
111
|
-
assert.equal(post.body.config_src, 'cloudflare');
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
await ta('putConfiguration sends ttyd ingress + 404 catch-all', async () => {
|
|
115
|
-
let seen = null;
|
|
116
|
-
const { fetchImpl } = makeFetch({
|
|
117
|
-
'PUT /accounts/acct123/cfd_tunnel/tun1/configurations': ({ body }) => { seen = body; return { config: body.config }; },
|
|
118
|
-
});
|
|
119
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
120
|
-
await cf.putConfiguration('tun1', { hostname: 'term-alice.dev.example.com' });
|
|
121
|
-
assert.deepEqual(seen.config.ingress[0], { hostname: 'term-alice.dev.example.com', service: 'http://127.0.0.1:7681' });
|
|
122
|
-
assert.deepEqual(seen.config.ingress[1], { service: 'http_status:404' });
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
await ta('getTunnelToken unwraps the CF envelope to the raw token string', async () => {
|
|
126
|
-
const { fetchImpl } = makeFetch({
|
|
127
|
-
'GET /accounts/acct123/cfd_tunnel/tun1/token': () => 'the-connector-token',
|
|
128
|
-
});
|
|
129
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
130
|
-
assert.equal(await cf.getTunnelToken('tun1'), 'the-connector-token');
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
console.log('DNS routing (live, fake fetch):');
|
|
134
|
-
|
|
135
|
-
await ta('upsertDnsRoute POSTs a proxied CNAME to cfargotunnel when absent', async () => {
|
|
136
|
-
let posted = null;
|
|
137
|
-
const { fetchImpl } = makeFetch({
|
|
138
|
-
'GET /zones/zone1/dns_records': () => [],
|
|
139
|
-
'POST /zones/zone1/dns_records': ({ body }) => { posted = body; return { id: 'rec1', ...body }; },
|
|
140
|
-
});
|
|
141
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
142
|
-
await cf.upsertDnsRoute({ zoneId: 'zone1', name: 'term-alice.dev.example.com', tunnelId: 'tun1' });
|
|
143
|
-
assert.equal(posted.type, 'CNAME');
|
|
144
|
-
assert.equal(posted.content, 'tun1.cfargotunnel.com');
|
|
145
|
-
assert.equal(posted.proxied, true);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
await ta('upsertDnsRoute PUTs (updates) when the record already exists', async () => {
|
|
149
|
-
let method = null;
|
|
150
|
-
const { fetchImpl } = makeFetch({
|
|
151
|
-
'GET /zones/zone1/dns_records': () => [{ id: 'rec-existing', name: 'term-alice.dev.example.com' }],
|
|
152
|
-
'PUT /zones/zone1/dns_records/rec-existing': ({ body }) => { method = 'PUT'; return { id: 'rec-existing', ...body }; },
|
|
153
|
-
});
|
|
154
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
155
|
-
await cf.upsertDnsRoute({ zoneId: 'zone1', name: 'term-alice.dev.example.com', tunnelId: 'tun1' });
|
|
156
|
-
assert.equal(method, 'PUT');
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
await ta('getZoneId throws a clear error when the zone is not found', async () => {
|
|
160
|
-
const { fetchImpl } = makeFetch({ 'GET /zones': () => [] });
|
|
161
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
162
|
-
await assert.rejects(() => cf.getZoneId('nope.com'), /not found/);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
await ta('CF envelope success:false throws CfApiError with the message', async () => {
|
|
166
|
-
const { fetchImpl } = makeFetch({
|
|
167
|
-
'GET /accounts/acct123/cfd_tunnel': () => ({ status: 200, json: { success: false, errors: [{ message: 'bad scope' }] } }),
|
|
168
|
-
});
|
|
169
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
170
|
-
await assert.rejects(() => cf.findTunnelByName('x'), /bad scope/);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
console.log('high-level compose:');
|
|
174
|
-
|
|
175
|
-
await ta('ensureNamedTunnel composes create→configure→token→zone→dns and returns bake values', async () => {
|
|
176
|
-
const { fetchImpl, calls } = makeFetch({
|
|
177
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
178
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
179
|
-
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => ({ config: body.config }),
|
|
180
|
-
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
181
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
182
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
183
|
-
'POST /zones/zoneZ/dns_records': ({ body }) => ({ id: 'recZ', ...body }),
|
|
184
|
-
});
|
|
185
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
186
|
-
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
187
|
-
assert.equal(r.tunnelId, 'tunZ');
|
|
188
|
-
assert.equal(r.token, 'tokZ');
|
|
189
|
-
assert.equal(r.hostname, 'term-carol.dev.example.com');
|
|
190
|
-
// the create used the deterministic per-builder name
|
|
191
|
-
assert.ok(calls.some((c) => c.method === 'POST' && c.path.endsWith('/cfd_tunnel') && c.body.name === 'otb-term-carol'));
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
await ta('teardownNamedTunnel deletes DNS + tunnel and never throws', async () => {
|
|
195
|
-
const { fetchImpl, calls } = makeFetch({
|
|
196
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
197
|
-
'GET /zones/zoneZ/dns_records': () => [{ id: 'recZ', name: 'term-dan.dev.example.com' }],
|
|
198
|
-
'DELETE /zones/zoneZ/dns_records/recZ': () => ({ id: 'recZ' }),
|
|
199
|
-
'GET /accounts/acct123/cfd_tunnel': () => [{ id: 'tunZ', name: 'otb-term-dan' }],
|
|
200
|
-
'DELETE /accounts/acct123/cfd_tunnel/tunZ/connections': () => ({}),
|
|
201
|
-
'DELETE /accounts/acct123/cfd_tunnel/tunZ': () => ({ id: 'tunZ' }),
|
|
202
|
-
});
|
|
203
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
204
|
-
const out = await cf.teardownNamedTunnel({ login: 'dan', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
205
|
-
assert.equal(out.dnsDeleted, true);
|
|
206
|
-
assert.equal(out.tunnelDeleted, true);
|
|
207
|
-
assert.deepEqual(out.errors, []);
|
|
208
|
-
assert.ok(calls.some((c) => c.method === 'DELETE' && c.path === '/accounts/acct123/cfd_tunnel/tunZ'));
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
await ta('teardownNamedTunnel on a missing tunnel/record is graceful (no throw, flags false)', async () => {
|
|
212
|
-
const { fetchImpl } = makeFetch({
|
|
213
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
214
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
215
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
216
|
-
});
|
|
217
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
218
|
-
const out = await cf.teardownNamedTunnel({ login: 'ghost', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
219
|
-
assert.equal(out.dnsDeleted, false);
|
|
220
|
-
assert.equal(out.tunnelDeleted, false);
|
|
221
|
-
assert.deepEqual(out.errors, []);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
await ta('teardown collects errors per-leg without throwing', async () => {
|
|
225
|
-
const { fetchImpl } = makeFetch({
|
|
226
|
-
'GET /zones': () => ({ status: 403, json: { success: false, errors: [{ message: 'no dns scope' }] } }),
|
|
227
|
-
'GET /accounts/acct123/cfd_tunnel': () => [{ id: 'tunZ', name: 'otb-term- err' }],
|
|
228
|
-
});
|
|
229
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
230
|
-
const out = await cf.teardownNamedTunnel({ login: 'err', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
231
|
-
assert.ok(out.errors.some((e) => /dns/.test(e)), 'dns failure recorded');
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
console.log('game preview ingress (ADR 0044):');
|
|
235
|
-
|
|
236
|
-
await ta('putConfiguration inserts extraRoutes between the primary host and the 404 catch-all', async () => {
|
|
237
|
-
let seen = null;
|
|
238
|
-
const { fetchImpl } = makeFetch({
|
|
239
|
-
'PUT /accounts/acct123/cfd_tunnel/tun1/configurations': ({ body }) => { seen = body; return { config: body.config }; },
|
|
240
|
-
});
|
|
241
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
242
|
-
await cf.putConfiguration('tun1', {
|
|
243
|
-
hostname: 'term-alice.dev.example.com',
|
|
244
|
-
extraRoutes: [{ hostname: 'sandbox-alice.dev.example.com', service: GAME_SERVICE }],
|
|
245
|
-
});
|
|
246
|
-
assert.equal(seen.config.ingress.length, 3, 'ttyd + game + catch-all');
|
|
247
|
-
assert.deepEqual(seen.config.ingress[0], { hostname: 'term-alice.dev.example.com', service: 'http://127.0.0.1:7681' });
|
|
248
|
-
assert.deepEqual(seen.config.ingress[1], { hostname: 'sandbox-alice.dev.example.com', service: 'http://127.0.0.1:3000' });
|
|
249
|
-
assert.deepEqual(seen.config.ingress[2], { service: 'http_status:404' }, 'catch-all stays last');
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
await ta('ensureNamedTunnel adds the game preview route + its own CNAME and returns previewHostname', async () => {
|
|
253
|
-
let putBody = null;
|
|
254
|
-
const dnsPosts = [];
|
|
255
|
-
const { fetchImpl } = makeFetch({
|
|
256
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
257
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
258
|
-
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => { putBody = body; return { config: body.config }; },
|
|
259
|
-
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
260
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
261
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
262
|
-
'POST /zones/zoneZ/dns_records': ({ body }) => { dnsPosts.push(body.name); return { id: `rec-${body.name}`, ...body }; },
|
|
263
|
-
});
|
|
264
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
265
|
-
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
266
|
-
// returns the deterministic preview host alongside the terminal host
|
|
267
|
-
assert.equal(r.previewHostname, 'sandbox-carol.dev.example.com');
|
|
268
|
-
assert.equal(r.hostname, 'term-carol.dev.example.com');
|
|
269
|
-
// the ingress now carries the game route at 127.0.0.1:3000
|
|
270
|
-
assert.deepEqual(putBody.config.ingress[1], { hostname: 'sandbox-carol.dev.example.com', service: GAME_SERVICE });
|
|
271
|
-
// BOTH hostnames get a proxied CNAME
|
|
272
|
-
assert.ok(dnsPosts.includes('term-carol.dev.example.com'), 'terminal CNAME upserted');
|
|
273
|
-
assert.ok(dnsPosts.includes('sandbox-carol.dev.example.com'), 'preview CNAME upserted');
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
await ta('teardownNamedTunnel also deletes the preview CNAME', async () => {
|
|
277
|
-
const deletedDns = [];
|
|
278
|
-
const { fetchImpl } = makeFetch({
|
|
279
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
280
|
-
'GET /zones/zoneZ/dns_records': ({ query }) => {
|
|
281
|
-
const n = query.name;
|
|
282
|
-
if (n === 'term-dan.dev.example.com') return [{ id: 'recTerm', name: n }];
|
|
283
|
-
if (n === 'sandbox-dan.dev.example.com') return [{ id: 'recGame', name: n }];
|
|
284
|
-
return [];
|
|
285
|
-
},
|
|
286
|
-
'DELETE /zones/zoneZ/dns_records/recTerm': () => { deletedDns.push('term'); return { id: 'recTerm' }; },
|
|
287
|
-
'DELETE /zones/zoneZ/dns_records/recGame': () => { deletedDns.push('game'); return { id: 'recGame' }; },
|
|
288
|
-
'GET /accounts/acct123/cfd_tunnel': () => [{ id: 'tunZ', name: 'otb-term-dan' }],
|
|
289
|
-
'DELETE /accounts/acct123/cfd_tunnel/tunZ/connections': () => ({}),
|
|
290
|
-
'DELETE /accounts/acct123/cfd_tunnel/tunZ': () => ({ id: 'tunZ' }),
|
|
291
|
-
});
|
|
292
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
293
|
-
const out = await cf.teardownNamedTunnel({ login: 'dan', zone: 'example.com', terminalZone: 'dev.example.com' });
|
|
294
|
-
assert.equal(out.dnsDeleted, true);
|
|
295
|
-
assert.equal(out.previewDnsDeleted, true, 'preview CNAME removed');
|
|
296
|
-
assert.equal(out.tunnelDeleted, true);
|
|
297
|
-
assert.deepEqual(out.errors, []);
|
|
298
|
-
assert.deepEqual(deletedDns.sort(), ['game', 'term']);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
console.log('edge cookie strip (#919 / SR#11):');
|
|
302
|
-
|
|
303
|
-
const RULESET_PATH = '/zones/zoneZ/rulesets/phases/http_request_late_transform/entrypoint';
|
|
304
|
-
|
|
305
|
-
await ta('ensureCookieStripRule PUTs a remove-Cookie rule scoped to term-*/sandbox-*', async () => {
|
|
306
|
-
let put = null;
|
|
307
|
-
const { fetchImpl } = makeFetch({
|
|
308
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
309
|
-
[`GET ${RULESET_PATH}`]: () => ({ id: 'rs1', rules: [] }),
|
|
310
|
-
[`PUT ${RULESET_PATH}`]: ({ body }) => { put = body; return { id: 'rs1', rules: body.rules }; },
|
|
311
|
-
});
|
|
312
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
313
|
-
await cf.ensureCookieStripRule({ zone: 'example.com' });
|
|
314
|
-
assert.equal(put.rules.length, 1, 'one rule on a previously-empty phase');
|
|
315
|
-
const rule = put.rules[0];
|
|
316
|
-
assert.equal(rule.action, 'rewrite');
|
|
317
|
-
assert.deepEqual(rule.action_parameters.headers.Cookie, { operation: 'remove' });
|
|
318
|
-
assert.match(rule.expression, /term-/);
|
|
319
|
-
assert.match(rule.expression, /sandbox-/);
|
|
320
|
-
assert.equal(rule.enabled, true);
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
await ta('ensureCookieStripRule preserves OTHER transform rules and is idempotent on ours', async () => {
|
|
324
|
-
let put = null;
|
|
325
|
-
const otherRule = { id: 'keepme', version: '3', last_updated: 'x', action: 'rewrite', action_parameters: { headers: { 'X-Foo': { operation: 'set', value: 'bar' } } }, expression: 'true', description: 'someone-elses-rule', enabled: true };
|
|
326
|
-
const ourStale = { id: 'oldours', action: 'rewrite', action_parameters: { headers: { Cookie: { operation: 'remove' } } }, expression: 'starts_with(http.host, "term-")', description: 'otb-919 strip Cookie on box subdomains (term-*/sandbox-*) — SR#11', enabled: true };
|
|
327
|
-
const { fetchImpl } = makeFetch({
|
|
328
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
329
|
-
[`GET ${RULESET_PATH}`]: () => ({ id: 'rs1', rules: [otherRule, ourStale] }),
|
|
330
|
-
[`PUT ${RULESET_PATH}`]: ({ body }) => { put = body; return { id: 'rs1', rules: body.rules }; },
|
|
331
|
-
});
|
|
332
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
333
|
-
await cf.ensureCookieStripRule({ zone: 'example.com' });
|
|
334
|
-
// exactly two rules: the foreign one (kept) + ONE fresh copy of ours (no dupes)
|
|
335
|
-
assert.equal(put.rules.length, 2);
|
|
336
|
-
const ours = put.rules.filter((r) => /otb-919/.test(r.description));
|
|
337
|
-
assert.equal(ours.length, 1, 'our rule is de-duplicated, not appended twice');
|
|
338
|
-
const kept = put.rules.find((r) => r.description === 'someone-elses-rule');
|
|
339
|
-
assert.ok(kept, 'a foreign transform rule must be preserved');
|
|
340
|
-
// read-only CF fields must be stripped from the preserved rule (a PUT rejects them)
|
|
341
|
-
assert.equal(kept.id, undefined);
|
|
342
|
-
assert.equal(kept.version, undefined);
|
|
343
|
-
assert.equal(kept.last_updated, undefined);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
await ta('ensureCookieStripRule creates the phase entrypoint when none exists (404 → PUT)', async () => {
|
|
347
|
-
let put = null;
|
|
348
|
-
const { fetchImpl } = makeFetch({
|
|
349
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
350
|
-
[`GET ${RULESET_PATH}`]: () => ({ status: 404, json: { success: false, errors: [{ message: 'ruleset not found' }] } }),
|
|
351
|
-
[`PUT ${RULESET_PATH}`]: ({ body }) => { put = body; return { id: 'rs-new', rules: body.rules }; },
|
|
352
|
-
});
|
|
353
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
354
|
-
await cf.ensureCookieStripRule({ zone: 'example.com' });
|
|
355
|
-
assert.equal(put.rules.length, 1, 'a 404 entrypoint is treated as empty, our rule is created');
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
await ta('ensureCookieStripRule failure does NOT block ensureNamedTunnel (best-effort)', async () => {
|
|
359
|
-
// No rulesets routes → the cookie-strip PUT 404s and throws, but ensureNamedTunnel
|
|
360
|
-
// swallows it. The tunnel + DNS still complete and bake values come back.
|
|
361
|
-
const { fetchImpl } = makeFetch({
|
|
362
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
363
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
364
|
-
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => ({ config: body.config }),
|
|
365
|
-
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
366
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
367
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
368
|
-
'POST /zones/zoneZ/dns_records': ({ body }) => ({ id: `rec-${body.name}`, ...body }),
|
|
369
|
-
// GET ruleset returns 404 (no route) → swallowed; PUT ruleset 404 → bubbles to
|
|
370
|
-
// ensureNamedTunnel's try/catch and is logged, not thrown.
|
|
371
|
-
});
|
|
372
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
373
|
-
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'example.com', terminalZone: 'example.com' });
|
|
374
|
-
assert.equal(r.token, 'tokZ', 'provisioning completes despite the cookie-strip rule failing');
|
|
375
|
-
assert.equal(r.hostname, 'term-carol.example.com');
|
|
376
|
-
// task 1002511: the failure is reported STRUCTURALLY, not only logged, so box.js
|
|
377
|
-
// can surface it in the provision summary instead of burying it mid-run.
|
|
378
|
-
assert.equal(r.edgeRules.cookieStrip.applied, false, 'a failed cookie-strip PUT must never report applied');
|
|
379
|
-
assert.ok(r.edgeRules.cookieStrip.error, 'the failure reason is captured');
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
console.log('edge cache bypass (#1065):');
|
|
383
|
-
|
|
384
|
-
const CACHE_PATH = '/zones/zoneZ/rulesets/phases/http_request_cache_settings/entrypoint';
|
|
385
|
-
|
|
386
|
-
await ta('ensureCacheBypassRule PUTs a cache:false rule scoped to sandbox-* (not the apex)', async () => {
|
|
387
|
-
let put = null;
|
|
388
|
-
const { fetchImpl } = makeFetch({
|
|
389
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
390
|
-
[`GET ${CACHE_PATH}`]: () => ({ id: 'rs1', rules: [] }),
|
|
391
|
-
[`PUT ${CACHE_PATH}`]: ({ body }) => { put = body; return { id: 'rs1', rules: body.rules }; },
|
|
392
|
-
});
|
|
393
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
394
|
-
await cf.ensureCacheBypassRule({ zone: 'example.com' });
|
|
395
|
-
assert.equal(put.rules.length, 1);
|
|
396
|
-
const rule = put.rules[0];
|
|
397
|
-
assert.equal(rule.action, 'set_cache_settings');
|
|
398
|
-
assert.equal(rule.action_parameters.cache, false);
|
|
399
|
-
assert.match(rule.expression, /sandbox-/);
|
|
400
|
-
assert.doesNotMatch(rule.expression, /term-/, 'ttyd hosts are not cache-ruled');
|
|
401
|
-
// prod-safety: the apex must NOT satisfy the expression
|
|
402
|
-
assert.ok(!'example.com'.startsWith('sandbox-'), 'apex never matches sandbox-*');
|
|
403
|
-
assert.equal(rule.enabled, true);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
await ta('ensureCacheBypassRule preserves OTHER cache rules and is idempotent on ours', async () => {
|
|
407
|
-
let put = null;
|
|
408
|
-
const otherRule = { id: 'keepme', version: '2', last_updated: 'x', action: 'set_cache_settings', action_parameters: { cache: true }, expression: 'starts_with(http.host, "img-")', description: 'someone-elses-cache-rule', enabled: true };
|
|
409
|
-
const ourStale = { id: 'oldours', action: 'set_cache_settings', action_parameters: { cache: false }, expression: 'starts_with(http.host, "sandbox-")', description: CACHE_BYPASS_RULE_DESC, enabled: true };
|
|
410
|
-
const { fetchImpl } = makeFetch({
|
|
411
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
412
|
-
[`GET ${CACHE_PATH}`]: () => ({ id: 'rs1', rules: [otherRule, ourStale] }),
|
|
413
|
-
[`PUT ${CACHE_PATH}`]: ({ body }) => { put = body; return { id: 'rs1', rules: body.rules }; },
|
|
414
|
-
});
|
|
415
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
416
|
-
await cf.ensureCacheBypassRule({ zone: 'example.com' });
|
|
417
|
-
assert.equal(put.rules.length, 2);
|
|
418
|
-
assert.equal(put.rules.filter((r) => r.description === CACHE_BYPASS_RULE_DESC).length, 1, 'our rule de-duplicated');
|
|
419
|
-
const kept = put.rules.find((r) => r.description === 'someone-elses-cache-rule');
|
|
420
|
-
assert.ok(kept, 'a foreign cache rule must be preserved');
|
|
421
|
-
assert.equal(kept.id, undefined);
|
|
422
|
-
assert.equal(kept.version, undefined);
|
|
423
|
-
assert.equal(kept.last_updated, undefined);
|
|
424
|
-
});
|
|
425
|
-
|
|
426
|
-
await ta('ensureCacheBypassRule creates the cache phase entrypoint when none exists (404 → PUT)', async () => {
|
|
427
|
-
let put = null;
|
|
428
|
-
const { fetchImpl } = makeFetch({
|
|
429
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
430
|
-
[`GET ${CACHE_PATH}`]: () => ({ status: 404, json: { success: false, errors: [{ message: 'ruleset not found' }] } }),
|
|
431
|
-
[`PUT ${CACHE_PATH}`]: ({ body }) => { put = body; return { id: 'rs-new', rules: body.rules }; },
|
|
432
|
-
});
|
|
433
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
434
|
-
await cf.ensureCacheBypassRule({ zone: 'example.com' });
|
|
435
|
-
assert.equal(put.rules.length, 1);
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
await ta('ensureCacheBypassRule failure does NOT block ensureNamedTunnel (best-effort)', async () => {
|
|
439
|
-
// tunnel + DNS routes succeed; both ruleset PUTs 404 (no route) → swallowed by
|
|
440
|
-
// ensureNamedTunnel's try/catch. Provisioning still returns the bake values.
|
|
441
|
-
const { fetchImpl } = makeFetch({
|
|
442
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
443
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
444
|
-
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => ({ config: body.config }),
|
|
445
|
-
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
446
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
447
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
448
|
-
'POST /zones/zoneZ/dns_records': ({ body }) => ({ id: `rec-${body.name}`, ...body }),
|
|
449
|
-
});
|
|
450
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
451
|
-
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'example.com', terminalZone: 'example.com' });
|
|
452
|
-
assert.equal(r.token, 'tokZ', 'provisioning completes despite the cache rule failing');
|
|
453
|
-
assert.equal(r.edgeRules.cacheBypass.applied, false);
|
|
454
|
-
assert.ok(r.edgeRules.cacheBypass.error, 'the cache-rule failure reason is captured');
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
await ta('ensureNamedTunnel reports edgeRules.applied on the success path (both legs land)', async () => {
|
|
458
|
-
const RS = '/zones/zoneZ/rulesets/phases/http_request_late_transform/entrypoint';
|
|
459
|
-
const CS = '/zones/zoneZ/rulesets/phases/http_request_cache_settings/entrypoint';
|
|
460
|
-
const { fetchImpl } = makeFetch({
|
|
461
|
-
'GET /accounts/acct123/cfd_tunnel': () => [],
|
|
462
|
-
'POST /accounts/acct123/cfd_tunnel': ({ body }) => ({ id: 'tunZ', name: body.name }),
|
|
463
|
-
'PUT /accounts/acct123/cfd_tunnel/tunZ/configurations': ({ body }) => ({ config: body.config }),
|
|
464
|
-
'GET /accounts/acct123/cfd_tunnel/tunZ/token': () => 'tokZ',
|
|
465
|
-
'GET /zones': () => [{ id: 'zoneZ' }],
|
|
466
|
-
'GET /zones/zoneZ/dns_records': () => [],
|
|
467
|
-
'POST /zones/zoneZ/dns_records': ({ body }) => ({ id: `rec-${body.name}`, ...body }),
|
|
468
|
-
[`GET ${RS}`]: () => ({ id: 'rs1', rules: [] }),
|
|
469
|
-
[`PUT ${RS}`]: ({ body }) => ({ id: 'rs1', rules: body.rules }),
|
|
470
|
-
[`GET ${CS}`]: () => ({ id: 'rs2', rules: [] }),
|
|
471
|
-
[`PUT ${CS}`]: ({ body }) => ({ id: 'rs2', rules: body.rules }),
|
|
472
|
-
});
|
|
473
|
-
const cf = new CfTunnelApi({ dryRun: false, token: TOKEN, accountId: ACCT, fetchImpl });
|
|
474
|
-
const r = await cf.ensureNamedTunnel({ login: 'carol', zone: 'example.com', terminalZone: 'example.com' });
|
|
475
|
-
assert.equal(r.edgeRules.cookieStrip.applied, true);
|
|
476
|
-
assert.equal(r.edgeRules.cookieStrip.error, null);
|
|
477
|
-
assert.equal(r.edgeRules.cacheBypass.applied, true);
|
|
478
|
-
assert.equal(r.token, 'tokZ', 'the bake values still come back');
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
await ta('ensureNamedTunnel in DRY-RUN reports applied:false (a plan never applied anything)', async () => {
|
|
482
|
-
const cf = new CfTunnelApi({ accountId: ACCT, fetchImpl: () => { throw new Error('should not fetch'); } });
|
|
483
|
-
const r = await cf.ensureNamedTunnel({ login: 'alice', zone: 'example.com', terminalZone: 'example.com' });
|
|
484
|
-
assert.equal(r.edgeRules.cookieStrip.applied, false, 'dry-run must not read as "the security rule is in place"');
|
|
485
|
-
assert.equal(r.edgeRules.cacheBypass.applied, false);
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
console.log('name helpers:');
|
|
489
|
-
t('tunnelName / terminalHostname / previewHostname are deterministic', () => {
|
|
490
|
-
assert.equal(tunnelName('alice'), 'otb-term-alice');
|
|
491
|
-
assert.equal(terminalHostname('alice', 'dev.example.com'), 'term-alice.dev.example.com');
|
|
492
|
-
assert.equal(previewHostname('alice', 'example.com'), 'sandbox-alice.example.com');
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
console.log(`\ncf-tunnel: ${passed} passed, ${failed} failed`);
|
|
496
|
-
process.exit(failed === 0 ? 0 : 1);
|