Complete forge delivery regression coverage
This commit is contained in:
@@ -2,25 +2,12 @@ package gitea
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.maximumdirect.net/releasekit/internal/artifact"
|
||||
)
|
||||
|
||||
type AssetState string
|
||||
|
||||
const (
|
||||
AssetMatching AssetState = "matching"
|
||||
AssetMissing AssetState = "missing"
|
||||
AssetConflicting AssetState = "conflicting"
|
||||
AssetUnverifiable AssetState = "unverifiable"
|
||||
)
|
||||
|
||||
type RemoteAsset struct {
|
||||
@@ -30,18 +17,6 @@ type RemoteAsset struct {
|
||||
DownloadURL string
|
||||
}
|
||||
|
||||
type AssetInspection struct {
|
||||
Expected artifact.ExpectedAsset
|
||||
State AssetState
|
||||
Existing []RemoteAsset
|
||||
Err error
|
||||
}
|
||||
|
||||
type Inspection struct {
|
||||
Assets []AssetInspection
|
||||
Unrelated []RemoteAsset
|
||||
}
|
||||
|
||||
type assetResponse struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
@@ -49,66 +24,6 @@ type assetResponse struct {
|
||||
BrowserDownloadURL string `json:"browser_download_url"`
|
||||
}
|
||||
|
||||
// InspectAssets lists the release's attachments and compares every declared
|
||||
// asset by streamed SHA-256 content. It does not create or modify remote data.
|
||||
func (c Client) InspectAssets(ctx context.Context, destination Destination, releaseID int64, expected []artifact.ExpectedAsset) (Inspection, error) {
|
||||
if releaseID <= 0 {
|
||||
return Inspection{}, failure(FailureInvalid, "inspect release assets", 0, errors.New("release ID must be positive"))
|
||||
}
|
||||
if strings.TrimSpace(destination.Token) == "" {
|
||||
return Inspection{}, failure(FailureInvalid, "inspect release assets", 0, errors.New("Gitea token is required"))
|
||||
}
|
||||
base, err := parseBaseURL(destination.BaseURL)
|
||||
if err != nil {
|
||||
return Inspection{}, failure(FailureInvalid, "validate destination", 0, err)
|
||||
}
|
||||
if strings.TrimSpace(destination.Owner) == "" || strings.ContainsAny(destination.Owner, "/\\") || strings.TrimSpace(destination.Repository) == "" || strings.ContainsAny(destination.Repository, "/\\") {
|
||||
return Inspection{}, failure(FailureInvalid, "inspect release assets", 0, errors.New("Gitea owner and repository must be path segments"))
|
||||
}
|
||||
if err := artifact.ValidateExpectedAssets(expected); err != nil {
|
||||
return Inspection{}, failure(FailureInvalid, "inspect release assets", 0, err)
|
||||
}
|
||||
apiRoot := strings.TrimRight(base.String(), "/") + "/api/v1/repos/" + url.PathEscape(destination.Owner) + "/" + url.PathEscape(destination.Repository)
|
||||
listed, err := c.listAssets(ctx, c.httpClient(), destination.Token, releaseID, apiRoot)
|
||||
if err != nil {
|
||||
return Inspection{}, err
|
||||
}
|
||||
remoteByName := make(map[string][]RemoteAsset, len(listed))
|
||||
for _, item := range listed {
|
||||
remoteByName[item.Filename] = append(remoteByName[item.Filename], item)
|
||||
}
|
||||
inspection := Inspection{Assets: make([]AssetInspection, 0, len(expected))}
|
||||
declared := make(map[string]struct{}, len(expected))
|
||||
for _, item := range expected {
|
||||
declared[item.Filename] = struct{}{}
|
||||
matches := remoteByName[item.Filename]
|
||||
observation := AssetInspection{Expected: item, Existing: matches}
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
observation.State = AssetMissing
|
||||
case 1:
|
||||
remoteHash, _, err := c.downloadHash(ctx, c.httpClient(), base, destination.Token, matches[0].DownloadURL)
|
||||
if err != nil {
|
||||
observation.State = AssetUnverifiable
|
||||
observation.Err = err
|
||||
} else if remoteHash == item.SHA256 {
|
||||
observation.State = AssetMatching
|
||||
} else {
|
||||
observation.State = AssetConflicting
|
||||
}
|
||||
default:
|
||||
observation.State = AssetConflicting
|
||||
}
|
||||
inspection.Assets = append(inspection.Assets, observation)
|
||||
}
|
||||
for _, item := range listed {
|
||||
if _, expected := declared[item.Filename]; !expected {
|
||||
inspection.Unrelated = append(inspection.Unrelated, item)
|
||||
}
|
||||
}
|
||||
return inspection, nil
|
||||
}
|
||||
|
||||
func (c Client) listAssets(ctx context.Context, client *http.Client, token string, releaseID int64, apiRoot string) ([]RemoteAsset, error) {
|
||||
if releaseID <= 0 {
|
||||
return nil, failure(FailureInvalid, "list release assets", 0, errors.New("release ID must be positive"))
|
||||
@@ -131,20 +46,6 @@ func (c Client) listAssets(ctx context.Context, client *http.Client, token strin
|
||||
return assets, nil
|
||||
}
|
||||
|
||||
func (c Client) downloadHash(ctx context.Context, client *http.Client, base *url.URL, token, rawURL string) (string, int64, error) {
|
||||
stream, err := c.openAsset(ctx, client, base, token, rawURL)
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
defer stream.Close()
|
||||
hasher := sha256.New()
|
||||
size, err := io.Copy(hasher, stream)
|
||||
if err != nil {
|
||||
return "", size, failure(FailureUnverifiable, "read release asset", 0, err)
|
||||
}
|
||||
return hex.EncodeToString(hasher.Sum(nil)), size, nil
|
||||
}
|
||||
|
||||
func (c Client) openAsset(ctx context.Context, client *http.Client, base *url.URL, token, rawURL string) (io.ReadCloser, error) {
|
||||
if strings.TrimSpace(rawURL) == "" {
|
||||
return nil, failure(FailureUnverifiable, "download release asset", 0, errors.New("attachment has no download URL"))
|
||||
|
||||
Reference in New Issue
Block a user