package busstate import ( "bufio" "encoding/json" "io" "testing" "time" stdio "git.novox.be/novox/mesh-sdk/go" "git.novox.be/novox/mesh-sdk/go/buscalls" ) // A bundle keeps objects in its own object store through the runtime (ADR 0595 §2, §9; A3's request for distribution's // incoming): each call is the runtime's method of buscalls, naming the store by its local name, and answers as the // runtime answered; a method that is no bus-state call is refused before the runtime is asked. func TestABundleKeepsObjectsThroughTheRuntime(t *testing.T) { t.Setenv(stdio.ServedAs, "distribution") inR, inW := io.Pipe() outR, outW := io.Pipe() type result struct { what string err error got any } results := make(chan result, 8) go func() { _ = stdio.ServeOn("", []stdio.Tool{{Name: "keep", Run: func(map[string]any) (any, error) { go func() { in := ObjectsOf("incoming") info, err := in.Put("t1.part0", []byte("hello"), "a part") results <- result{"put", err, info.Digest} data, err := in.Get("t1.part0") results <- result{"get", err, string(data.Data)} list, err := in.List() results <- result{"list", err, len(list)} results <- result{"delete", in.Delete("t1.part0"), nil} fill, err := in.Fill() results <- result{"fill", err, fill.MaxBytes} _, err = stdio.BusState("mesh/state.get", nil) results <- result{"refused", err, nil} }() return "started", nil }}}, inR, outW) }() lines := bufio.NewScanner(outR) next := func() map[string]any { done := make(chan map[string]any, 1) go func() { if lines.Scan() { var m map[string]any _ = json.Unmarshal(lines.Bytes(), &m) done <- m } }() select { case m := <-done: return m case <-time.After(3 * time.Second): t.Fatal("the bundle said nothing") return nil } } send := func(m map[string]any) { b, _ := json.Marshal(m); _, _ = inW.Write(append(b, '\n')) } send(map[string]any{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": map[string]any{"name": "keep"}}) answers := map[string]any{ buscalls.ObjectsPut: buscalls.ObjectInfo{Name: "t1.part0", Size: 5, Digest: "SHA-256=x"}, buscalls.ObjectsGet: buscalls.ObjectData{Info: buscalls.ObjectInfo{Name: "t1.part0"}, Data: []byte("hello")}, buscalls.ObjectsList: buscalls.ObjectsListed{Objects: []buscalls.ObjectInfo{{Name: "t1.part0"}}}, buscalls.ObjectsDelete: buscalls.Done{}, buscalls.ObjectsInfo: buscalls.Fill{Count: 0, MaxBytes: 20 << 30}, } asked := map[string]map[string]any{} for len(asked) < len(answers) { m := next() method, _ := m["method"].(string) if method == "" { continue // the tool's own answer } p, _ := m["params"].(map[string]any) asked[method] = p send(map[string]any{"jsonrpc": "2.0", "id": m["id"], "result": answers[method]}) } want := map[string]any{"put": "SHA-256=x", "get": "hello", "list": 1, "delete": nil, "fill": uint64(20 << 30)} for range 5 { r := <-results if r.err != nil || r.got != want[r.what] { t.Errorf("%s answered %v, %v; want %v", r.what, r.got, r.err, want[r.what]) } } if r := <-results; r.err == nil { t.Error("a call that is not on the bundle's bus state was asked of the runtime") } for method, p := range asked { if p["objects"] != "incoming" { t.Errorf("%s named the store %v, not its local name", method, p["objects"]) } } if p := asked[buscalls.ObjectsPut]; p["name"] != "t1.part0" || p["data"] != "aGVsbG8=" || p["description"] != "a part" { t.Errorf("put asked %v", p) } }