Files
compute/routers/main_test.go
zeekayandHanzo Dev 689c7ac2d9 the worker nodes become an op the document can see
GET /v1/k8s/nodes is now zip.Get[controllers.Scope, controllers.Nodes]. Until
now it was a handler and nothing else: the route existed on the wire and in none
of the projections, so the OpenAPI document, the MCP tool list, the CLI and every
generated SDK did not know it was there — which is how cloud came to hand-write a
client for a shape visor never published.

A typed op answers its Out directly, so the wire moved: {"nodes":[...]} instead
of {status,msg,data}. The key is ALWAYS written, empty list included, because an
absent field is how a reader tells "this service does not serve this op" from
"this org has no nodes" — and on a fold that feeds the world fleet, confusing
those two is a silently smaller estate. cloud moves with it in the same breath.

It resolves its org through principal, the rule the agent ops already established
one commit ago, so the whole compute surface answers "whose org is this" in one
place. AgentScope becomes Scope: "the caller and nothing else" is one shape, and
one shape under two names is two schemas a generated client has to learn. The
precedence that rule turns on — a signed Owner claim beats a typed ?owner, always
— had no test; it does now, against a real RS256 token minted in the test, and it
goes red if the two branches are swapped.

And a dead gate. routers' tests ran with a NIL authz.Enforcer, because InitAuthz
is called only from the real bootstrap: every request through ApiFilter reached
Enforce on nil, panicked, and came back 500. "This route is gated", written as
"not 200", was passing on the strength of a nil dereference and would have gone
on passing with the policy deleted. TestMain builds the real (in-memory, static)
policy, so a 403 now means 403 — and the new route asserts on the DENIAL TEXT,
because the handler refuses an org-less request with a 403 of its own and the
status alone cannot tell the two apart.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 00:35:45 -07:00

40 lines
1.5 KiB
Go

// Copyright 2026 Hanzo Industries Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routers
import (
"os"
"testing"
"github.com/hanzoai/visor/authz"
)
// TestMain builds the authorizer the way the server does, because without it
// these tests were not measuring authorization at all.
//
// authz.Enforcer is a package variable and InitAuthz is called from exactly one
// place — pkg/visor/embed.go, the real bootstrap. A test that routes a request
// through ApiFilter without it reaches Enforcer.Enforce on a NIL enforcer, which
// panics, which Recover turns into a 500. A "this route is gated" assertion
// written as "not 200" then passes on the strength of a nil dereference, and
// would go on passing with the policy deleted.
//
// The policy is a static string and the enforcer is in memory — no store, no
// network — so building the real one costs nothing and makes a 403 mean 403.
func TestMain(m *testing.M) {
authz.InitAuthz()
os.Exit(m.Run())
}