Refactor GitHub workflows for Registry Client SDK tests

- Add environment variables for registry initialization in both `pr-test.yml` and `unit-test.yml`.
- Implement a waiting mechanism for the registry service to ensure readiness before executing tests.
- Remove redundant steps for user creation and service restart, streamlining the test setup process.
- Enhance the client code to support auto-discovery of the API prefix, improving flexibility in server URL configuration.
- Introduce comprehensive error handling in client tests to cover various edge cases and improve test coverage.
This commit is contained in:
Max 2026-03-02 21:39:22 +08:00
parent 691802ae62
commit 35ab1d1040
5 changed files with 430 additions and 69 deletions

View file

@ -1378,6 +1378,9 @@ jobs:
image: yaoapp/registry:latest
ports:
- "8080:8080"
env:
REGISTRY_INIT_USER: yaoagents
REGISTRY_INIT_PASS: yaoagents
strategy:
matrix:
go: ["1.25"]
@ -1428,6 +1431,17 @@ jobs:
body: '🤖 Registry Client SDK Tests running...'
});
- name: Wait for Registry
run: |
for i in $(seq 1 15); do
if curl -sf http://localhost:8080/.well-known/yao-registry > /dev/null 2>&1; then
echo "Registry is ready"
break
fi
echo "Waiting for registry... ($i)"
sleep 1
done
- name: Checkout Kun
uses: actions/checkout@v4
with:
@ -1493,23 +1507,6 @@ jobs:
with:
go-version: ${{ matrix.go }}
- name: Create Registry Test User
run: |
docker exec ${{ job.services.yao-registry.id }} \
registry user add --password yaoagents yaoagents
- name: Restart Registry Service
run: |
docker restart ${{ job.services.yao-registry.id }}
for i in $(seq 1 15); do
if curl -sf http://localhost:8080/.well-known/yao-registry > /dev/null 2>&1; then
echo "Registry is ready"
break
fi
echo "Waiting for registry... ($i)"
sleep 1
done
- name: Run Registry Client Tests
env:
YAO_REGISTRY_URL: http://localhost:8080

View file

@ -1042,10 +1042,24 @@ jobs:
image: yaoapp/registry:latest
ports:
- "8080:8080"
env:
REGISTRY_INIT_USER: yaoagents
REGISTRY_INIT_PASS: yaoagents
strategy:
matrix:
go: ["1.25"]
steps:
- name: Wait for Registry
run: |
for i in $(seq 1 15); do
if curl -sf http://localhost:8080/.well-known/yao-registry > /dev/null 2>&1; then
echo "Registry is ready"
break
fi
echo "Waiting for registry... ($i)"
sleep 1
done
- name: Checkout Kun
uses: actions/checkout@v4
with:
@ -1109,23 +1123,6 @@ jobs:
with:
go-version: ${{ matrix.go }}
- name: Create Registry Test User
run: |
docker exec ${{ job.services.yao-registry.id }} \
registry user add --password yaoagents yaoagents
- name: Restart Registry Service
run: |
docker restart ${{ job.services.yao-registry.id }}
for i in $(seq 1 15); do
if curl -sf http://localhost:8080/.well-known/yao-registry > /dev/null 2>&1; then
echo "Registry is ready"
break
fi
echo "Waiting for registry... ($i)"
sleep 1
done
- name: Run Registry Client Tests
env:
YAO_REGISTRY_URL: http://localhost:8080

56
registry/README.md Normal file
View file

@ -0,0 +1,56 @@
# registry
Go client SDK for [Yao Registry](https://github.com/YaoApp/registry).
## Usage
```go
import "github.com/yaoapp/yao/registry"
// Only the server URL is required. API prefix is auto-discovered
// via /.well-known/yao-registry on the first call.
c := registry.New("https://registry.yaoagents.com",
registry.WithAuth("user", "pass"), // optional, for push/delete
)
// Push a .yao.zip package
result, err := c.Push("assistants", "@yao", "hello", "1.0.0", zipBytes)
// Pull (by version or dist-tag)
data, digest, err := c.Pull("assistants", "@yao", "hello", "latest")
// Query
pack, err := c.GetPackument("assistants", "@yao", "hello")
ver, err := c.GetVersion("assistants", "@yao", "hello", "1.0.0")
list, err := c.Search("hello", "assistants", 1, 20)
// Dependencies
deps, err := c.GetDependencies("assistants", "@yao", "hello", "1.0.0", true)
// Dist-tags
c.SetTag("assistants", "@yao", "hello", "stable", "1.0.0")
c.DeleteTag("assistants", "@yao", "hello", "stable")
// Delete
c.DeleteVersion("assistants", "@yao", "hello", "1.0.0")
```
## Options
| Option | Description |
|--------|-------------|
| `WithAuth(user, pass)` | Basic Auth for push/delete |
| `WithHTTPClient(hc)` | Custom `*http.Client` |
| `WithTimeout(d)` | HTTP timeout |
## Environment
Tests require a running registry server. Set `YAO_REGISTRY_URL` (default `http://localhost:8080`) and create user `yaoagents`/`yaoagents`.
```bash
# Start registry with test user
REGISTRY_INIT_USER=yaoagents REGISTRY_INIT_PASS=yaoagents registry start
# Run tests
go test ./registry/... -v
```

View file

@ -11,15 +11,20 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"
)
// Client talks to a Yao Registry server over HTTP.
// On first API call it discovers the API prefix via /.well-known/yao-registry
// so callers only need to provide the server root URL (e.g. "https://registry.yaoagents.com").
type Client struct {
baseURL string
username string
password string
httpClient *http.Client
baseURL string
apiPrefix string // resolved from well-known, e.g. "/v1"
discoverOnce sync.Once
username string
password string
httpClient *http.Client
}
// Option configures a Client.
@ -43,10 +48,13 @@ func WithTimeout(d time.Duration) Option {
return func(c *Client) { c.httpClient.Timeout = d }
}
// New creates a registry client. serverURL is the base URL without trailing slash.
// New creates a registry client. serverURL is the root URL users configure,
// e.g. "http://localhost:8080" or "https://registry.yaoagents.com".
// The actual API prefix is auto-discovered via /.well-known/yao-registry.
func New(serverURL string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(serverURL, "/"),
apiPrefix: "/v1", // sensible default, overridden by discovery
httpClient: &http.Client{Timeout: 60 * time.Second},
}
for _, o := range opts {
@ -55,6 +63,16 @@ func New(serverURL string, opts ...Option) *Client {
return c
}
// ensureDiscovered runs well-known discovery exactly once (thread-safe).
func (c *Client) ensureDiscovered() {
c.discoverOnce.Do(func() {
var info RegistryInfo
if err := c.doGet("/.well-known/yao-registry", nil, &info); err == nil && info.Registry.API != "" {
c.apiPrefix = strings.TrimRight(info.Registry.API, "/")
}
})
}
// --- Response types ---
// RegistryInfo is returned by the discovery endpoint.
@ -184,16 +202,17 @@ func (e *APIError) Error() string {
// Discover calls GET /.well-known/yao-registry.
func (c *Client) Discover() (*RegistryInfo, error) {
var info RegistryInfo
if err := c.get("/.well-known/yao-registry", nil, &info); err != nil {
if err := c.doGet("/.well-known/yao-registry", nil, &info); err != nil {
return nil, err
}
return &info, nil
}
// Info calls GET /v1/.
// Info calls GET {apiPrefix}/.
func (c *Client) Info() (*ServerInfo, error) {
c.ensureDiscovered()
var info ServerInfo
if err := c.get("/v1/", nil, &info); err != nil {
if err := c.doGet(c.apiPrefix+"/", nil, &info); err != nil {
return nil, err
}
return &info, nil
@ -201,8 +220,9 @@ func (c *Client) Info() (*ServerInfo, error) {
// --- List & Search ---
// List calls GET /v1/:type with optional filters.
// List calls GET {apiPrefix}/:type with optional filters.
func (c *Client) List(pkgType string, scope string, query string, page, pageSize int) (*ListResult, error) {
c.ensureDiscovered()
params := url.Values{}
if scope != "" {
params.Set("scope", scope)
@ -217,14 +237,15 @@ func (c *Client) List(pkgType string, scope string, query string, page, pageSize
params.Set("pagesize", fmt.Sprintf("%d", pageSize))
}
var result ListResult
if err := c.get("/v1/"+pkgType, params, &result); err != nil {
if err := c.doGet(c.apiPrefix+"/"+pkgType, params, &result); err != nil {
return nil, err
}
return &result, nil
}
// Search calls GET /v1/search.
// Search calls GET {apiPrefix}/search.
func (c *Client) Search(q string, pkgType string, page, pageSize int) (*ListResult, error) {
c.ensureDiscovered()
params := url.Values{"q": {q}}
if pkgType != "" {
params.Set("type", pkgType)
@ -236,7 +257,7 @@ func (c *Client) Search(q string, pkgType string, page, pageSize int) (*ListResu
params.Set("pagesize", fmt.Sprintf("%d", pageSize))
}
var result ListResult
if err := c.get("/v1/search", params, &result); err != nil {
if err := c.doGet(c.apiPrefix+"/search", params, &result); err != nil {
return nil, err
}
return &result, nil
@ -244,21 +265,23 @@ func (c *Client) Search(q string, pkgType string, page, pageSize int) (*ListResu
// --- Package metadata ---
// GetPackument calls GET /v1/:type/:scope/:name.
// GetPackument calls GET {apiPrefix}/:type/:scope/:name.
func (c *Client) GetPackument(pkgType, scope, name string) (*Packument, error) {
c.ensureDiscovered()
var p Packument
path := fmt.Sprintf("/v1/%s/%s/%s", pkgType, scope, name)
if err := c.get(path, nil, &p); err != nil {
path := fmt.Sprintf("%s/%s/%s/%s", c.apiPrefix, pkgType, scope, name)
if err := c.doGet(path, nil, &p); err != nil {
return nil, err
}
return &p, nil
}
// GetVersion calls GET /v1/:type/:scope/:name/:version.
// GetVersion calls GET {apiPrefix}/:type/:scope/:name/:version.
func (c *Client) GetVersion(pkgType, scope, name, version string) (*VersionDetail, error) {
c.ensureDiscovered()
var v VersionDetail
path := fmt.Sprintf("/v1/%s/%s/%s/%s", pkgType, scope, name, version)
if err := c.get(path, nil, &v); err != nil {
path := fmt.Sprintf("%s/%s/%s/%s/%s", c.apiPrefix, pkgType, scope, name, version)
if err := c.doGet(path, nil, &v); err != nil {
return nil, err
}
return &v, nil
@ -266,25 +289,27 @@ func (c *Client) GetVersion(pkgType, scope, name, version string) (*VersionDetai
// --- Dependencies ---
// GetDependencies calls GET /v1/:type/:scope/:name/:version/dependencies.
// GetDependencies calls GET {apiPrefix}/:type/:scope/:name/:version/dependencies.
func (c *Client) GetDependencies(pkgType, scope, name, version string, recursive bool) (*DependencyList, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/%s/dependencies", pkgType, scope, name, version)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/%s/dependencies", c.apiPrefix, pkgType, scope, name, version)
params := url.Values{}
if recursive {
params.Set("recursive", "true")
}
var dl DependencyList
if err := c.get(path, params, &dl); err != nil {
if err := c.doGet(path, params, &dl); err != nil {
return nil, err
}
return &dl, nil
}
// GetDependents calls GET /v1/:type/:scope/:name/dependents.
// GetDependents calls GET {apiPrefix}/:type/:scope/:name/dependents.
func (c *Client) GetDependents(pkgType, scope, name string) (*DependentList, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/dependents", pkgType, scope, name)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/dependents", c.apiPrefix, pkgType, scope, name)
var dl DependentList
if err := c.get(path, nil, &dl); err != nil {
if err := c.doGet(path, nil, &dl); err != nil {
return nil, err
}
return &dl, nil
@ -292,9 +317,10 @@ func (c *Client) GetDependents(pkgType, scope, name string) (*DependentList, err
// --- Push & Pull ---
// Push uploads a .yao.zip package via PUT /v1/:type/:scope/:name/:version.
// Push uploads a .yao.zip package via PUT {apiPrefix}/:type/:scope/:name/:version.
func (c *Client) Push(pkgType, scope, name, version string, zipData []byte) (*PushResult, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/%s", pkgType, scope, name, version)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/%s", c.apiPrefix, pkgType, scope, name, version)
req, err := http.NewRequest(http.MethodPut, c.baseURL+path, bytes.NewReader(zipData))
if err != nil {
return nil, err
@ -319,10 +345,11 @@ func (c *Client) Push(pkgType, scope, name, version string, zipData []byte) (*Pu
return &result, nil
}
// Pull downloads a .yao.zip via GET /v1/:type/:scope/:name/:version/pull.
// Pull downloads a .yao.zip via GET {apiPrefix}/:type/:scope/:name/:version/pull.
// The version parameter can be a semver or a dist-tag name.
func (c *Client) Pull(pkgType, scope, name, version string) ([]byte, string, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/%s/pull", pkgType, scope, name, version)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/%s/pull", c.apiPrefix, pkgType, scope, name, version)
resp, err := c.httpClient.Get(c.baseURL + path)
if err != nil {
return nil, "", err
@ -343,9 +370,10 @@ func (c *Client) Pull(pkgType, scope, name, version string) ([]byte, string, err
// --- Tags ---
// SetTag calls PUT /v1/:type/:scope/:name/tags/:tag.
// SetTag calls PUT {apiPrefix}/:type/:scope/:name/tags/:tag.
func (c *Client) SetTag(pkgType, scope, name, tag, version string) (*TagResult, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/tags/%s", pkgType, scope, name, tag)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/tags/%s", c.apiPrefix, pkgType, scope, name, tag)
body, _ := json.Marshal(map[string]string{"version": version})
req, err := http.NewRequest(http.MethodPut, c.baseURL+path, bytes.NewReader(body))
@ -372,9 +400,10 @@ func (c *Client) SetTag(pkgType, scope, name, tag, version string) (*TagResult,
return &result, nil
}
// DeleteTag calls DELETE /v1/:type/:scope/:name/tags/:tag.
// DeleteTag calls DELETE {apiPrefix}/:type/:scope/:name/tags/:tag.
func (c *Client) DeleteTag(pkgType, scope, name, tag string) (*TagDeleteResult, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/tags/%s", pkgType, scope, name, tag)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/tags/%s", c.apiPrefix, pkgType, scope, name, tag)
req, err := http.NewRequest(http.MethodDelete, c.baseURL+path, nil)
if err != nil {
return nil, err
@ -400,9 +429,10 @@ func (c *Client) DeleteTag(pkgType, scope, name, tag string) (*TagDeleteResult,
// --- Delete ---
// DeleteVersion calls DELETE /v1/:type/:scope/:name/:version.
// DeleteVersion calls DELETE {apiPrefix}/:type/:scope/:name/:version.
func (c *Client) DeleteVersion(pkgType, scope, name, version string) (*DeleteResult, error) {
path := fmt.Sprintf("/v1/%s/%s/%s/%s", pkgType, scope, name, version)
c.ensureDiscovered()
path := fmt.Sprintf("%s/%s/%s/%s/%s", c.apiPrefix, pkgType, scope, name, version)
req, err := http.NewRequest(http.MethodDelete, c.baseURL+path, nil)
if err != nil {
return nil, err
@ -434,7 +464,7 @@ func (c *Client) setAuth(req *http.Request) {
}
}
func (c *Client) get(path string, params url.Values, out interface{}) error {
func (c *Client) doGet(path string, params url.Values, out interface{}) error {
u := c.baseURL + path
if len(params) > 0 {
u += "?" + params.Encode()

View file

@ -2,6 +2,7 @@ package registry_test
import (
"net/http"
"net/http/httptest"
"os"
"testing"
"time"
@ -705,6 +706,88 @@ func TestDeleteNonExistentTag(t *testing.T) {
c.DeleteVersion("assistants", testScope, name, "1.0.0")
}
// --- Push / Delete error responses ---
func TestPushDuplicateVersion(t *testing.T) {
c := newClient()
zipData, _ := testdata.BuildZip(&testdata.Manifest{
Type: "assistant", Scope: testScope, Name: "dup-push", Version: "1.0.0",
}, nil)
defer cleanup(c, "assistants", testScope, "dup-push", "1.0.0")
_, err := c.Push("assistants", testScope, "dup-push", "1.0.0", zipData)
if err != nil {
t.Fatalf("first push failed: %v", err)
}
_, err = c.Push("assistants", testScope, "dup-push", "1.0.0", zipData)
if err == nil {
t.Fatal("expected error on duplicate push")
}
apiErr, ok := err.(*registry.APIError)
if !ok {
t.Fatalf("expected APIError, got %T", err)
}
if apiErr.StatusCode != 409 {
t.Errorf("expected 409, got %d", apiErr.StatusCode)
}
c.DeleteVersion("assistants", testScope, "dup-push", "1.0.0")
}
func TestDeleteNonExistentVersion(t *testing.T) {
c := newClient()
_, err := c.DeleteVersion("assistants", testScope, "never-existed", "9.9.9")
if err == nil {
t.Fatal("expected error deleting non-existent version")
}
apiErr, ok := err.(*registry.APIError)
if !ok {
t.Fatalf("expected APIError, got %T", err)
}
if apiErr.StatusCode != 404 {
t.Errorf("expected 404, got %d", apiErr.StatusCode)
}
}
func TestSetTagNonExistentPackage(t *testing.T) {
c := newClient()
_, err := c.SetTag("assistants", testScope, "no-such-pkg", "beta", "1.0.0")
if err == nil {
t.Fatal("expected error setting tag on non-existent package")
}
apiErr, ok := err.(*registry.APIError)
if !ok {
t.Fatalf("expected APIError, got %T", err)
}
if apiErr.StatusCode != 404 {
t.Errorf("expected 404, got %d", apiErr.StatusCode)
}
}
func TestListWithQueryFilter(t *testing.T) {
c := newClient()
zipData, _ := testdata.BuildZip(&testdata.Manifest{
Type: "mcp", Scope: testScope, Name: "list-query-mcp", Version: "1.0.0",
Description: "filterable mcp tool",
}, nil)
defer cleanup(c, "mcps", testScope, "list-query-mcp", "1.0.0")
c.Push("mcps", testScope, "list-query-mcp", "1.0.0", zipData)
list, err := c.List("mcps", testScope, "filterable", 1, 10)
if err != nil {
t.Fatalf("List with scope+query failed: %v", err)
}
if list.Total < 1 {
t.Errorf("expected at least 1 result, got %d", list.Total)
}
c.DeleteVersion("mcps", testScope, "list-query-mcp", "1.0.0")
}
// --- Release type CRUD ---
func TestReleaseCRUD(t *testing.T) {
@ -759,3 +842,201 @@ func TestReleaseCRUD(t *testing.T) {
t.Fatalf("Delete failed: %v", err)
}
}
// --- httptest-based edge-case coverage ---
func TestParseErrorNonJSON(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("plain text error"))
}))
defer srv.Close()
c := registry.New(srv.URL, registry.WithAuth("u", "p"))
_, err := c.Push("assistants", "@t", "x", "1.0.0", []byte("data"))
if err == nil {
t.Fatal("expected error")
}
apiErr, ok := err.(*registry.APIError)
if !ok {
t.Fatalf("expected APIError, got %T", err)
}
if apiErr.Message != "plain text error" {
t.Errorf("expected plain text body in message, got %q", apiErr.Message)
}
}
func TestInvalidBaseURL(t *testing.T) {
c := registry.New("http://invalid\x7f:8080", registry.WithAuth("u", "p"))
_, err := c.Push("assistants", "@t", "x", "1.0.0", []byte("zip"))
if err == nil {
t.Error("expected error from Push with invalid URL")
}
_, err = c.SetTag("assistants", "@t", "x", "beta", "1.0.0")
if err == nil {
t.Error("expected error from SetTag with invalid URL")
}
_, err = c.DeleteTag("assistants", "@t", "x", "beta")
if err == nil {
t.Error("expected error from DeleteTag with invalid URL")
}
_, err = c.DeleteVersion("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Error("expected error from DeleteVersion with invalid URL")
}
}
func TestPullNonOKStatus(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"error":"forbidden"}`))
}))
defer srv.Close()
c := registry.New(srv.URL)
_, _, err := c.Pull("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Fatal("expected error for forbidden pull")
}
apiErr, ok := err.(*registry.APIError)
if !ok {
t.Fatalf("expected APIError, got %T", err)
}
if apiErr.StatusCode != 403 {
t.Errorf("expected 403, got %d", apiErr.StatusCode)
}
}
func TestSetTagDeleteTagError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"bad request"}`))
}))
defer srv.Close()
c := registry.New(srv.URL, registry.WithAuth("u", "p"))
_, err := c.SetTag("assistants", "@t", "x", "beta", "1.0.0")
if err == nil {
t.Error("expected error from SetTag")
}
_, err = c.DeleteTag("assistants", "@t", "x", "beta")
if err == nil {
t.Error("expected error from DeleteTag")
}
}
func TestDeleteVersionError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusConflict)
w.Write([]byte(`{"error":"conflict"}`))
}))
defer srv.Close()
c := registry.New(srv.URL, registry.WithAuth("u", "p"))
_, err := c.DeleteVersion("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Fatal("expected error")
}
}
func TestMalformedResponseBody(t *testing.T) {
callCount := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
callCount++
switch {
case r.Method == http.MethodPut && r.URL.Path != "/tags/" && callCount <= 2:
w.WriteHeader(http.StatusCreated)
w.Write([]byte("not-json"))
case r.Method == http.MethodPut:
w.WriteHeader(http.StatusOK)
w.Write([]byte("not-json"))
case r.Method == http.MethodDelete:
w.WriteHeader(http.StatusOK)
w.Write([]byte("not-json"))
default:
w.WriteHeader(http.StatusOK)
w.Write([]byte("not-json"))
}
}))
defer srv.Close()
c := registry.New(srv.URL, registry.WithAuth("u", "p"))
_, err := c.Push("assistants", "@t", "x", "1.0.0", []byte("zip"))
if err == nil {
t.Error("expected decode error from Push")
}
_, err = c.SetTag("assistants", "@t", "x", "beta", "1.0.0")
if err == nil {
t.Error("expected decode error from SetTag")
}
_, err = c.DeleteTag("assistants", "@t", "x", "beta")
if err == nil {
t.Error("expected decode error from DeleteTag")
}
_, err = c.DeleteVersion("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Error("expected decode error from DeleteVersion")
}
}
func TestPullReadBodyError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Length", "99999")
w.WriteHeader(http.StatusOK)
w.Write([]byte("short"))
}))
defer srv.Close()
c := registry.New(srv.URL)
data, _, err := c.Pull("assistants", "@t", "x", "1.0.0")
if err != nil {
t.Logf("got expected error: %v", err)
return
}
if len(data) == 99999 {
t.Error("expected incomplete read")
}
}
func TestDoTransportError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
closedURL := srv.URL
srv.Close()
c := registry.New(closedURL, registry.WithAuth("u", "p"))
_, err := c.Push("assistants", "@t", "x", "1.0.0", []byte("zip"))
if err == nil {
t.Error("expected transport error from Push")
}
_, _, err = c.Pull("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Error("expected transport error from Pull")
}
_, err = c.SetTag("assistants", "@t", "x", "beta", "1.0.0")
if err == nil {
t.Error("expected transport error from SetTag")
}
_, err = c.DeleteTag("assistants", "@t", "x", "beta")
if err == nil {
t.Error("expected transport error from DeleteTag")
}
_, err = c.DeleteVersion("assistants", "@t", "x", "1.0.0")
if err == nil {
t.Error("expected transport error from DeleteVersion")
}
}