# test(trail): cover url wire-compat (old/new cli × old/new api)

`4bef6a1`→[main](/content/gh/entireio/cli/commits/main/index.html)·

toothbrush·3w ago·1 file·+42 added/-1 removed

Decode test proves a new server payload carrying `url` decodes into the
new TrailResource (new cli + new api) and is ignored by a struct without
the field (old cli + new api). new cli + old api is covered by
trailDisplayURL's fallback test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

## Sessions

6d7d67ae97b1View transcript

[?\
Add Web URLs to Trail CommandsClaude Code·Opus 4.8[1m]·1 step](/content/gh/entireio/cli/session/18a97739-bf62-4c0b-9cf2-9a1673f59583#timeline-6d7d67ae97b1/index.html)

## Changes

1

- cmd/entire/cli/api

- Mtrail_types_test.go+42/-1

```
1
2
3
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

package api

import "testing"
import (
	"encoding/json"
	"testing"
)

// TestTrailResourceDecodesServerURL covers the wire-compatibility matrix for the
// `url` field the API added:
//   - new cli + new api: the field decodes into TrailResource.URL and is used.
//   - old cli + new api: a client struct predating the field ignores the extra
//     key without error (Go's json.Unmarshal drops unknown fields), so an older
//     CLI keeps working against a newer server.
//
// (new cli + old api is exercised by trailDisplayURL's fallback in the cli pkg.)
func TestTrailResourceDecodesServerURL(t *testing.T) {
	t.Parallel()

// Shape a newer server would emit: includes `url`.
	payload := []byte(`{"id":"t1","number":640,"url":"https://entire.io/gh/o/r/trails/640/slug","branch":"feat/x","title":"T"}`)

// new cli + new api: URL is captured and available to display.
	var newClient TrailResource
	if err := json.Unmarshal(payload, &newClient); err != nil {
		t.Fatalf("new client failed to decode new payload: %v", err)
	}
	if newClient.URL != "https://entire.io/gh/o/r/trails/640/slug" {
		t.Fatalf("URL = %q, want server-provided url", newClient.URL)
	}

// old cli + new api: a struct without a URL field must not choke on the
	// extra key, and still decodes the fields it knows about.
	var oldClient struct {
		ID     string `json:"id"`
		Number int    `json:"number"`
		Title  string `json:"title"`
	}
	if err := json.Unmarshal(payload, &oldClient); err != nil {
		t.Fatalf("old client rejected new payload with extra url field: %v", err)
	}
	if oldClient.Number != 640 || oldClient.Title != "T" {
		t.Fatalf("old client decoded wrong values: %+v", oldClient)
	}
}

func TestTrailResourceToMetadataUsesID(t *testing.T) {
	t.Parallel()
```

Mcmd/entire/cli/api/trail_types_test.go+42/-1
