Skip to content

Commit a1e4260

Browse files
authored
Merge pull request #85 from buildkite/chore_integration_test
chore: add integration tests based and align client with api
2 parents bc40047 + cf8f621 commit a1e4260

File tree

5 files changed

+592
-18
lines changed

5 files changed

+592
-18
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ dist/
33
coverage.*
44
/zstash
55
/docs
6+
*.log

api/client.go

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,36 @@ var (
2828
ErrCacheEntryNotFound = errors.New("cache entry not found")
2929
)
3030

31-
// isJSONContentType checks if the content type indicates JSON response
32-
// Handles cases like "application/json" and "application/json; charset=utf-8"
33-
func isJSONContentType(contentType string) bool {
34-
// Remove any leading/trailing whitespace and convert to lowercase
35-
contentType = strings.TrimSpace(strings.ToLower(contentType))
36-
37-
// Check if it starts with "application/json"
38-
return strings.HasPrefix(contentType, "application/json")
31+
// CacheClient defines the interface for cache API operations.
32+
// This interface is implemented by Client and can be mocked for testing.
33+
type CacheClient interface {
34+
// CacheRegistry retrieves information about a cache registry.
35+
// Returns the registry configuration or an error if not found.
36+
CacheRegistry(ctx context.Context, registry string) (CacheRegistryResp, error)
37+
38+
// CachePeekExists checks if a cache entry exists for the given key.
39+
// Returns the cache metadata, a boolean indicating existence (true if found), and any error.
40+
// When the cache entry is not found, returns (resp, false, nil) with resp.Message set.
41+
CachePeekExists(ctx context.Context, registry string, req CachePeekReq) (CachePeekResp, bool, error)
42+
43+
// CacheCreate creates a new cache entry and returns upload information.
44+
// Returns upload instructions including the upload ID and storage location.
45+
CacheCreate(ctx context.Context, registry string, req CacheCreateReq) (CacheCreateResp, error)
46+
47+
// CacheCommit marks a cache entry as committed after successful upload.
48+
// The upload ID from CacheCreate must be provided.
49+
CacheCommit(ctx context.Context, registry string, req CacheCommitReq) (CacheCommitResp, error)
50+
51+
// CacheRetrieve retrieves download information for a cache entry.
52+
// Returns the cache metadata, a boolean indicating if found (true if exists), and any error.
53+
// When the cache entry is not found, returns (resp, false, nil) with resp.Message set.
54+
// The response may indicate a fallback key was used via resp.Fallback.
55+
CacheRetrieve(ctx context.Context, registry string, req CacheRetrieveReq) (CacheRetrieveResp, bool, error)
3956
}
4057

58+
// Verify that Client implements CacheClient
59+
var _ CacheClient = (*Client)(nil)
60+
4161
type Client struct {
4262
client *http.Client
4363
endpoint string
@@ -89,11 +109,23 @@ type CachePeekReq struct {
89109
}
90110

91111
type CachePeekResp struct {
92-
Store string `json:"store"` // The store used for the cache entry
93-
Digest string `json:"digest"`
94-
ExpiresAt time.Time `json:"expires_at"`
95-
Compression string `json:"compression"`
96-
Message string `json:"message"`
112+
Store string `json:"store"` // The store used for the cache entry
113+
Digest string `json:"digest"`
114+
ExpiresAt time.Time `json:"expires_at"`
115+
Compression string `json:"compression"`
116+
Message string `json:"message"`
117+
FileSize int `json:"file_size"`
118+
Paths []string `json:"paths"`
119+
Pipeline string `json:"pipeline"`
120+
Branch string `json:"branch"`
121+
Owner string `json:"owner"`
122+
Platform string `json:"platform"`
123+
Key string `json:"key"`
124+
FallbackKeys []string `json:"fallback_keys"`
125+
CreatedAt time.Time `json:"created_at"`
126+
AgentID string `json:"agent_id"`
127+
JobID string `json:"job_id"`
128+
BuildID string `json:"build_id"`
97129
}
98130

99131
type CacheRegistryResp struct {
@@ -362,9 +394,32 @@ func doRequest[T any, V any](ctx context.Context, client *http.Client, method st
362394
}
363395

364396
// read the response body
365-
if err = json.NewDecoder(res.Body).Decode(&resp); err != nil {
397+
respBody, err := io.ReadAll(res.Body)
398+
if err != nil {
399+
return nil, resp, trace.NewError(span, "failed to read response body: %w", err)
400+
}
401+
402+
slog.Debug("API call", "method", method, "url", url, "status", res.StatusCode, "body", string(respBody))
403+
404+
if err = json.Unmarshal(respBody, &resp); err != nil {
366405
return nil, resp, trace.NewError(span, "failed to decode response body: %w", err)
367406
}
368407

369408
return res, resp, nil
370409
}
410+
411+
// isJSONContentType checks if the content type indicates JSON response
412+
// Handles cases like "application/json", "application/json; charset=utf-8",
413+
// and structured syntax suffixes like "application/problem+json"
414+
func isJSONContentType(contentType string) bool {
415+
// Remove any leading/trailing whitespace and convert to lowercase
416+
contentType = strings.TrimSpace(strings.ToLower(contentType))
417+
418+
// Remove any parameters (e.g., "; charset=utf-8")
419+
if idx := strings.Index(contentType, ";"); idx != -1 {
420+
contentType = strings.TrimSpace(contentType[:idx])
421+
}
422+
423+
// Check if it's application/json or application/*+json (e.g., application/problem+json)
424+
return contentType == "application/json" || strings.HasPrefix(contentType, "application/") && strings.HasSuffix(contentType, "+json")
425+
}

0 commit comments

Comments
 (0)