Files
node/config/spec/spec_test.go
T
Hanzo AI c3b398bc7b json: migrate every encoding/json import to json/v2 (go-json-experiment)
External (HTTP / JSON-RPC) is the only place JSON is legitimate. Every
existing encoding/json import in node/ moves to github.com/go-json-experiment/json
(v2 root, not the v1 sub-package). NewEncoder/NewDecoder rewrite to
MarshalWrite/UnmarshalRead. MarshalIndent rewrites to Marshal with
jsontext.WithIndent. json.RawMessage rewrites to jsontext.Value.
*json.SyntaxError rewrites to *jsontext.SyntacticError.

81 files migrated. LLM.md captures the rule + v1->v2 delta table.

Known v2 semantic deltas surfaced by existing tests (followups, not regressions):
- [N]byte fields with no MarshalJSON now marshal as base64 string (v1 marshalled
  as JSON array of byte numbers). Affects vms/platformvm/txs/*_test.go fixtures
  with embedded BLS proofOfPossession.
- time.Duration has no v2 default representation; configs that wire-format
  Duration as nanoseconds (vms/{xvm,platformvm}/config, config/spec) need to
  switch to string-form Duration or carry an explicit option. v2 root does not
  re-export FormatDurationAsNano.
- v2 enforces strict UTF-8 (vms/chainadapter/messaging fixture has non-UTF-8).
- json.MarshalWrite does not append a trailing '\n' (v1 NewEncoder.Encode did);
  service/auth/auth_test.go expectation updated.
- nil []byte round-trips to empty (not nil); config_test deep-equal fixtures
  surface this.

All affected sites are at the API boundary; ZAP wire envelope already covers
the internal data paths (state, P2P, consensus, MPC, threshold). Internal
JSON sites that should move to ZAP next (separate work):
- vms/da/store.go            (DA blob/cert storage as JSON)
- vms/platformvm/airdrop     (airdrop claims as JSON in db)
- vms/chainadapter/appchain  (SQLite materializer schema/data blobs)
- vms/chainadapter/messaging (conversation codec)
- staking/kms.go             (KMS HTTP client — external technically, leave)
- utils/{bimap,ips}          (small marshaler shims — low priority)
2026-06-06 22:26:02 -07:00

202 lines
4.1 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package spec
import (
"github.com/go-json-experiment/json"
"testing"
)
func TestSpec(t *testing.T) {
s := Spec()
if s == nil {
t.Fatal("Spec() returned nil")
}
if s.Version == "" {
t.Error("spec version is empty")
}
if len(s.Flags) == 0 {
t.Error("spec has no flags")
}
if len(s.Categories) == 0 {
t.Error("spec has no categories")
}
}
func TestSpecHasExpectedFlags(t *testing.T) {
s := Spec()
// Check some key flags exist
expectedFlags := []string{
"network-id",
"log-level",
"http-port",
"db-type",
"consensus-sample-size",
"staking-port",
"api-admin-enabled",
}
for _, key := range expectedFlags {
if f := s.GetFlag(key); f == nil {
t.Errorf("expected flag %q not found", key)
}
}
}
func TestGetFlag(t *testing.T) {
s := Spec()
// Test existing flag
f := s.GetFlag("network-id")
if f == nil {
t.Fatal("GetFlag returned nil for network-id")
}
if f.Key != "network-id" {
t.Errorf("got key %q, want network-id", f.Key)
}
if f.Type != TypeString {
t.Errorf("got type %q, want %q", f.Type, TypeString)
}
if f.Category != CategoryNetwork {
t.Errorf("got category %q, want %q", f.Category, CategoryNetwork)
}
// Test non-existent flag
f = s.GetFlag("nonexistent-flag-xyz")
if f != nil {
t.Error("GetFlag should return nil for non-existent flag")
}
}
func TestFlagsByCategory(t *testing.T) {
s := Spec()
categories := []Category{
CategoryConsensus,
CategoryNetwork,
CategoryHTTP,
CategoryLogging,
CategoryStaking,
}
for _, cat := range categories {
flags := s.FlagsByCategory(cat)
if len(flags) == 0 {
t.Errorf("category %q has no flags", cat)
}
for _, f := range flags {
if f.Category != cat {
t.Errorf("flag %q has category %q, expected %q", f.Key, f.Category, cat)
}
}
}
}
func TestCategoryDescriptions(t *testing.T) {
s := Spec()
// All categories used in flags should have descriptions
usedCategories := make(map[Category]bool)
for _, f := range s.Flags {
usedCategories[f.Category] = true
}
for cat := range usedCategories {
if desc, ok := s.Categories[cat]; !ok {
t.Errorf("category %q has no description", cat)
} else if desc == "" {
t.Errorf("category %q has empty description", cat)
}
}
}
func TestSpecJSON(t *testing.T) {
s := Spec()
data, err := s.JSON()
if err != nil {
t.Fatalf("JSON() failed: %v", err)
}
// Verify it's valid JSON by unmarshaling
var parsed ConfigSpec
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("failed to unmarshal JSON: %v", err)
}
if parsed.Version != s.Version {
t.Errorf("version mismatch: got %q, want %q", parsed.Version, s.Version)
}
if len(parsed.Flags) != len(s.Flags) {
t.Errorf("flags count mismatch: got %d, want %d", len(parsed.Flags), len(s.Flags))
}
}
func TestFlagTypes(t *testing.T) {
s := Spec()
validTypes := map[FlagType]bool{
TypeBool: true,
TypeInt: true,
TypeUint: true,
TypeUint64: true,
TypeFloat64: true,
TypeDuration: true,
TypeString: true,
TypeStringSlice: true,
TypeIntSlice: true,
TypeStringToString: true,
}
for _, f := range s.Flags {
if !validTypes[f.Type] {
t.Errorf("flag %q has invalid type %q", f.Key, f.Type)
}
}
}
func TestFlagCount(t *testing.T) {
s := Spec()
// We expect a substantial number of flags
minExpected := 100
if len(s.Flags) < minExpected {
t.Errorf("expected at least %d flags, got %d", minExpected, len(s.Flags))
}
t.Logf("Total flags: %d", len(s.Flags))
// Log counts by category
counts := make(map[Category]int)
for _, f := range s.Flags {
counts[f.Category]++
}
for cat, count := range counts {
t.Logf(" %s: %d flags", cat, count)
}
}
func TestNoEmptyDescriptions(t *testing.T) {
s := Spec()
for _, f := range s.Flags {
if f.Description == "" {
t.Errorf("flag %q has empty description", f.Key)
}
}
}
func TestNoDuplicateKeys(t *testing.T) {
s := Spec()
seen := make(map[string]bool)
for _, f := range s.Flags {
if seen[f.Key] {
t.Errorf("duplicate flag key: %q", f.Key)
}
seen[f.Key] = true
}
}