package buildstep import ( "bytes" "encoding/json" "os" "path/filepath" "strings" "testing" "time" ) // The toolchains value and key are the layout all three node-build seats generate: each seat's fixture of the state // reads strictly into Toolchain, and the key is the seat's key form .. func TestTheToolchainsValueAndKeyAreTheSeatsLayout(t *testing.T) { read := 0 for _, seat := range []string{"node-build-go", "node-build-image", "node-build-node"} { raw, err := os.ReadFile(filepath.Join("..", "..", "contracts", "seats", seat, "fixtures", "states.json")) if err != nil { t.Fatal(err) } var fixtures []struct { Of string `json:"of"` Given json.RawMessage `json:"given"` } if err := json.Unmarshal(raw, &fixtures); err != nil { t.Fatal(err) } for _, f := range fixtures { if f.Of != "state:toolchains/." { continue } if back, err := DecodeToolchain(f.Given); err != nil || back.Version == "" { t.Errorf("%s: the fixture reads as %+v, %v", seat, back, err) } if _, err := EncodeToolchain(mustToolchain(t, f.Given)); err != nil { t.Errorf("%s: %v", seat, err) } dec := json.NewDecoder(bytes.NewReader(f.Given)) dec.DisallowUnknownFields() if err := dec.Decode(&Toolchain{}); err != nil { t.Errorf("%s: %v", seat, err) } read++ } } if read != 3 { t.Errorf("%d toolchain fixtures read, want 3", read) } raw, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27.1", Recipe: "3", At: time.Date(2026, 11, 5, 10, 0, 0, 0, time.UTC)}) if err != nil { t.Fatal(err) } if back, err := DecodeToolchain(raw); err != nil || back.Version != "go1.27.1" || back.At.IsZero() { t.Errorf("the value reads back as %+v, %v", back, err) } if key, err := ToolchainKey("ace", "typescript"); err != nil || key != "ace.typescript" { t.Errorf("key %q, %v", key, err) } for _, bad := range [][2]string{{"", "go"}, {"a.b", "go"}, {"ace", "rust"}} { if _, err := ToolchainKey(bad[0], bad[1]); err == nil { t.Errorf("the key of %v was made", bad) } } if _, err := EncodeToolchain(Toolchain{Language: "go", Version: "go1.27"}); err == nil { t.Error("a toolchain without its recipe was written") } if _, err := DecodeToolchain([]byte(`{"v":1,"language":"go","version":"x","recipe":"1","extra":1}`)); err == nil || !strings.Contains(err.Error(), "extra") { t.Errorf("a field the layout lacks was read: %v", err) } } func mustToolchain(t *testing.T, raw []byte) Toolchain { t.Helper() var tc Toolchain if err := json.Unmarshal(raw, &tc); err != nil { t.Fatal(err) } return tc }