T5 products in the client's inventory
CI / build (push) Successful in 25s

The inventory's product list (test.showInventory) gets the community products the person has: local packs, and
hub products in their library. Hiding one is answered by the proxy (and removes a hub product from the library).
When the library changes, the client is told to load its inventory again, with the server event IMVU sends
after a purchase. A product whose revision or info changes gets a new local ID, since the client keeps product
info for good; names lose characters the client's pages could take as HTML.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
dhkatzandClaude Opus 5.5 committed 2026-10-29 18:59:52 -07:00
1 parent c281fb572c
commit ca7a86ce89
8 files changed
+225 -5

No files matched your search

+47 -1
View File
@@ -99,6 +99,8 @@ func (f *fakeIMVU) RoundTrip(r *http.Request) (*http.Response, error) {
return reply(string((&xmlrpc.Response{Params: []xmlrpc.Value{xmlrpc.Struct{
{Name: "userId", Value: int64(7)}, {Name: "securityKey", Value: key},
{Name: "look", Value: []xmlrpc.Value{int64(80), int64(1)}}}}}).Marshal()))
case "test.showInventory":
return reply(string((&xmlrpc.Response{Params: []xmlrpc.Value{[]xmlrpc.Value{int64(1), int64(2)}}}).Marshal()))
case "test.inventoryProductBatch":
checkSigned(false)
var out []xmlrpc.Value
@@ -200,7 +202,8 @@ func TestCommunityProductEndToEnd(t *testing.T) {
if id, _ := ours.Get("products_id"); id != local {
t.Fatalf("entry %v", ours)
}
if name, _ := ours.Get("products_name"); name != "Blue <Coat>" {
// Names lose characters the client's pages could take as HTML.
if name, _ := ours.Get("products_name"); name != "Blue Coat" {
t.Fatalf("name %v", name)
}
if path, _ := ours.Get("cPath"); path != "106_40" {
@@ -315,3 +318,46 @@ func TestPackChangesAreServed(t *testing.T) {
t.Fatal("a removed pack is still served")
}
}
func TestInventory(t *testing.T) {
h, packs := newHarness(t)
hubDir := t.TempDir()
writePack(t, hubDir, "abcdefgh23", 1) // a hub product, kept by the fetcher
writePack(t, hubDir, "zzzzzzzz22", 1) // one someone in a room wore, not in the library
h.p.dirs = append(h.p.dirs, hubDir)
h.p.HubDir = hubDir
h.p.Owned = func(id string) bool { return id == "abcdefgh23" }
var hidden []string
h.p.OnHide = func(id string) { hidden = append(hidden, id) }
h.p.refresh(true)
local, hubLocal := h.p.byProductID("blue-coat").local, h.p.byProductID("abcdefgh23").local
api := "http://client-dynamic.imvu.com/api/xmlrpc/client.php"
_, body := h.do("POST", api, call("test.showInventory", int64(7), []xmlrpc.Value{int64(495)}, false), true, false)
r, err := xmlrpc.ParseResponse(body)
if err != nil {
t.Fatal(err)
}
got := fmt.Sprint(r.Params[0])
if want := fmt.Sprint([]xmlrpc.Value{int64(1), int64(2), local, hubLocal}); got != want {
t.Fatalf("inventory %s, want %s", got, want)
}
// Hiding a community product is answered here (and for hub products, takes them out of the library).
status, body := h.do("POST", "http://client-dynamic.imvu.com/api/service/inventory/inventory_delete.php",
[]byte(fmt.Sprintf("action=delete&cid=7&pid=%d", hubLocal)), true, false)
if status != 200 || !strings.Contains(string(body), `"success": true`) || len(hidden) != 1 || hidden[0] != "abcdefgh23" {
t.Fatalf("hide: %d %s %v", status, body, hidden)
}
// A product whose info changes gets a new local ID, so the client's product info cache shows the new info.
dir := filepath.Join(packs, "blue-coat")
manifest := `{"format": 1, "id": "blue-coat", "revision": 1, "name": "Red Coat", "creator": "Tester", "rating": "general"}`
os.WriteFile(filepath.Join(dir, "t5de-product.json"), []byte(manifest), 0o644)
h.p.refresh(true)
if again := h.p.byProductID("blue-coat").local; again == local {
t.Fatal("same local ID after a rename")
} else if id, _ := h.p.state.productID(again); id != "blue-coat" {
t.Fatalf("product of the new local ID: %q", id)
}
}