package hbrtl import ( "five/hbrt" "os" "path/filepath" "strings" "testing" ) // === hb_jsonEncode === func TestJsonEncode_String(t *testing.T) { _, th := setupVM() th.PushString("hello") th.PendingParams2(1) HbJsonEncode(th) if r := th.GetRetValue().AsString(); r != `"hello"` { t.Errorf("encode string = %q, want %q", r, `"hello"`) } } func TestJsonEncode_Number(t *testing.T) { _, th := setupVM() th.PushInt(42) th.PendingParams2(1) HbJsonEncode(th) if r := th.GetRetValue().AsString(); r != "42" { t.Errorf("encode int = %q, want %q", r, "42") } } func TestJsonEncode_Bool(t *testing.T) { _, th := setupVM() th.PushBool(true) th.PendingParams2(1) HbJsonEncode(th) if r := th.GetRetValue().AsString(); r != "true" { t.Errorf("encode bool = %q", r) } } func TestJsonEncode_Nil(t *testing.T) { _, th := setupVM() th.PushNil() th.PendingParams2(1) HbJsonEncode(th) if r := th.GetRetValue().AsString(); r != "null" { t.Errorf("encode nil = %q", r) } } func TestJsonEncode_Array(t *testing.T) { _, th := setupVM() arr := hbrt.MakeArrayFrom([]hbrt.Value{ hbrt.MakeInt(1), hbrt.MakeString("two"), hbrt.MakeBool(false), }) th.PushValue(arr) th.PendingParams2(1) HbJsonEncode(th) r := th.GetRetValue().AsString() if r != `[1,"two",false]` { t.Errorf("encode array = %q", r) } } func TestJsonEncode_Hash(t *testing.T) { _, th := setupVM() h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("name"), hbrt.MakeString("age")}, Values: []hbrt.Value{hbrt.MakeString("Five"), hbrt.MakeInt(1)}, } th.PushValue(hbrt.MakeHashFrom(h)) th.PendingParams2(1) HbJsonEncode(th) r := th.GetRetValue().AsString() if !strings.Contains(r, `"name":"Five"`) || !strings.Contains(r, `"age":1`) { t.Errorf("encode hash = %q", r) } } func TestJsonEncode_Pretty(t *testing.T) { _, th := setupVM() h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("x")}, Values: []hbrt.Value{hbrt.MakeInt(1)}, } th.PushValue(hbrt.MakeHashFrom(h)) th.PushBool(true) // pretty th.PendingParams2(2) HbJsonEncode(th) r := th.GetRetValue().AsString() if !strings.Contains(r, "\n") { t.Errorf("pretty should have newlines: %q", r) } } // === hb_jsonDecode === func TestJsonDecode_Object(t *testing.T) { _, th := setupVM() th.PushString(`{"name":"Five","version":2}`) th.PendingParams2(1) HbJsonDecode(th) r := th.GetRetValue() if !r.IsHash() { t.Fatalf("decode object should be hash, got %v", r) } } func TestJsonDecode_Array(t *testing.T) { _, th := setupVM() th.PushString(`[1,2,3]`) th.PendingParams2(1) HbJsonDecode(th) r := th.GetRetValue() if !r.IsArray() { t.Fatalf("decode array should be array") } arr := r.AsArray() if len(arr.Items) != 3 { t.Errorf("array len = %d, want 3", len(arr.Items)) } } func TestJsonDecode_Nested(t *testing.T) { _, th := setupVM() th.PushString(`{"users":[{"name":"Kim"},{"name":"Lee"}]}`) th.PendingParams2(1) HbJsonDecode(th) r := th.GetRetValue() if !r.IsHash() { t.Fatal("should be hash") } } func TestJsonDecode_Invalid(t *testing.T) { _, th := setupVM() th.PushString(`{broken}`) th.PendingParams2(1) HbJsonDecode(th) if !th.GetRetValue().IsNil() { t.Error("invalid JSON should return NIL") } } // === JsonPretty === func TestJsonPretty(t *testing.T) { _, th := setupVM() h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("a"), hbrt.MakeString("b")}, Values: []hbrt.Value{hbrt.MakeInt(1), hbrt.MakeInt(2)}, } th.PushValue(hbrt.MakeHashFrom(h)) th.PendingParams2(1) JsonPretty(th) r := th.GetRetValue().AsString() if !strings.Contains(r, "\n") || !strings.Contains(r, " ") { t.Errorf("pretty = %q", r) } } // === JsonPath === func TestJsonPath_Simple(t *testing.T) { _, th := setupVM() data := makeTestHash() th.PushValue(data) th.PushString("$.name") th.PendingParams2(2) JsonPath(th) if r := th.GetRetValue().AsString(); r != "Five" { t.Errorf("path $.name = %q, want 'Five'", r) } } func TestJsonPath_Nested(t *testing.T) { _, th := setupVM() // {"user":{"name":"Charles"}} inner := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("name")}, Values: []hbrt.Value{hbrt.MakeString("Charles")}, } outer := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("user")}, Values: []hbrt.Value{hbrt.MakeHashFrom(inner)}, } th.PushValue(hbrt.MakeHashFrom(outer)) th.PushString("$.user.name") th.PendingParams2(2) JsonPath(th) if r := th.GetRetValue().AsString(); r != "Charles" { t.Errorf("path $.user.name = %q", r) } } func TestJsonPath_Array(t *testing.T) { _, th := setupVM() arr := hbrt.MakeArrayFrom([]hbrt.Value{ hbrt.MakeInt(100), hbrt.MakeInt(200), hbrt.MakeInt(300), }) h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("scores")}, Values: []hbrt.Value{arr}, } th.PushValue(hbrt.MakeHashFrom(h)) th.PushString("$.scores[1]") th.PendingParams2(2) JsonPath(th) if r := th.GetRetValue().AsNumInt(); r != 200 { t.Errorf("path $.scores[1] = %d, want 200", r) } } func TestJsonPath_Missing(t *testing.T) { _, th := setupVM() th.PushValue(makeTestHash()) th.PushString("$.nonexistent.deep.path") th.PendingParams2(2) JsonPath(th) if !th.GetRetValue().IsNil() { t.Error("missing path should return NIL") } } // === JsonMerge === func TestJsonMerge(t *testing.T) { _, th := setupVM() a := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("x"), hbrt.MakeString("y")}, Values: []hbrt.Value{hbrt.MakeInt(1), hbrt.MakeInt(2)}, } b := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("y"), hbrt.MakeString("z")}, Values: []hbrt.Value{hbrt.MakeInt(99), hbrt.MakeInt(3)}, } th.PushValue(hbrt.MakeHashFrom(a)) th.PushValue(hbrt.MakeHashFrom(b)) th.PendingParams2(2) JsonMerge(th) r := th.GetRetValue() if !r.IsHash() { t.Fatal("merge should return hash") } h := r.AsHash() if len(h.Keys) != 3 { t.Errorf("merged keys = %d, want 3", len(h.Keys)) } } // === JsonValid === func TestJsonValid(t *testing.T) { _, th := setupVM() th.PushString(`{"valid":true}`) th.PendingParams2(1) JsonValid(th) if !th.GetRetValue().AsBool() { t.Error("valid JSON should return true") } th.PushString(`{broken`) th.PendingParams2(1) JsonValid(th) if th.GetRetValue().AsBool() { t.Error("invalid JSON should return false") } } // === JsonType === func TestJsonType(t *testing.T) { _, th := setupVM() cases := map[string]string{ `{"a":1}`: "object", `[1,2]`: "array", `"hello"`: "string", `42`: "number", `true`: "boolean", `null`: "null", ``: "invalid", } for input, expected := range cases { th.PushString(input) th.PendingParams2(1) JsonType(th) if r := th.GetRetValue().AsString(); r != expected { t.Errorf("JsonType(%q) = %q, want %q", input, r, expected) } } } // === JsonTo / JsonFrom (file roundtrip) === func TestJsonFileRoundtrip(t *testing.T) { _, th := setupVM() dir := t.TempDir() fpath := filepath.Join(dir, "test.json") // Write h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("msg"), hbrt.MakeString("num")}, Values: []hbrt.Value{hbrt.MakeString("hello"), hbrt.MakeInt(42)}, } th.PushValue(hbrt.MakeHashFrom(h)) th.PushString(fpath) th.PendingParams2(2) JsonTo(th) if !th.GetRetValue().AsBool() { t.Fatal("JsonTo failed") } // Verify file exists data, err := os.ReadFile(fpath) if err != nil { t.Fatal("file not created") } if !strings.Contains(string(data), "hello") { t.Errorf("file content: %s", string(data)) } // Read back th.PushString(fpath) th.PendingParams2(1) JsonFrom(th) r := th.GetRetValue() if !r.IsHash() { t.Fatal("JsonFrom should return hash") } } // === Encode/Decode roundtrip === func TestJsonRoundtrip(t *testing.T) { _, th := setupVM() // Complex nested structure inner := hbrt.MakeArrayFrom([]hbrt.Value{ hbrt.MakeInt(1), hbrt.MakeString("two"), hbrt.MakeBool(true), hbrt.MakeNil(), }) h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("arr"), hbrt.MakeString("val")}, Values: []hbrt.Value{inner, hbrt.MakeDouble(3.14, 0, 0)}, } // Encode th.PushValue(hbrt.MakeHashFrom(h)) th.PendingParams2(1) HbJsonEncode(th) encoded := th.GetRetValue().AsString() // Decode th.PushString(encoded) th.PendingParams2(1) HbJsonDecode(th) decoded := th.GetRetValue() // Re-encode and compare th.PushValue(decoded) th.PendingParams2(1) HbJsonEncode(th) reEncoded := th.GetRetValue().AsString() if encoded != reEncoded { t.Errorf("roundtrip mismatch:\n original: %s\n roundtrip: %s", encoded, reEncoded) } } // helper func makeTestHash() hbrt.Value { h := &hbrt.HbHash{ Keys: []hbrt.Value{hbrt.MakeString("name"), hbrt.MakeString("version")}, Values: []hbrt.Value{hbrt.MakeString("Five"), hbrt.MakeInt(1)}, } return hbrt.MakeHashFrom(h) }