Centralize dynamic-server command-key protection, profile runtime selection, and endpoint-resolution error construction; drop the dead dry-run catalog-miss branch; document host_compat stubs as edition-sync anchors; keep test fixtures out of the package dir via t.TempDir.
337 lines
14 KiB
Go
337 lines
14 KiB
Go
package app
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
authpkg "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/auth"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
type skillErrorReadCloser struct{ err error }
|
|
|
|
func (r skillErrorReadCloser) Read([]byte) (int, error) { return 0, r.err }
|
|
func (skillErrorReadCloser) Close() error { return nil }
|
|
|
|
func skillCoverageResponse(status int, body string) *http.Response {
|
|
return &http.Response{StatusCode: status, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header)}
|
|
}
|
|
|
|
func skillCoverageCommand() *cobra.Command {
|
|
cmd := &cobra.Command{}
|
|
cmd.SetContext(context.Background())
|
|
cmd.SetOut(io.Discard)
|
|
cmd.Flags().String("skill-id", "id", "")
|
|
cmd.Flags().String("query", "query", "")
|
|
cmd.Flags().String("source", "", "")
|
|
cmd.Flags().String("scopes", "scope", "")
|
|
return cmd
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSkillCommandHighLevelRemainingCoverage(t *testing.T) {
|
|
oldToken := skillLoadAccessToken
|
|
oldTmp := skillDownloadToTmp
|
|
oldHTTP := skillHTTPDo
|
|
oldNewRequest := skillNewRequest
|
|
oldTarget := skillResolveTargetPath
|
|
oldFetch := skillFetchDownloadInfo
|
|
oldDownload := skillDownloadFile
|
|
oldExtract := skillExtractZip
|
|
t.Cleanup(func() {
|
|
skillLoadAccessToken = oldToken
|
|
skillDownloadToTmp = oldTmp
|
|
skillHTTPDo = oldHTTP
|
|
skillNewRequest = oldNewRequest
|
|
skillResolveTargetPath = oldTarget
|
|
skillFetchDownloadInfo = oldFetch
|
|
skillDownloadFile = oldDownload
|
|
skillExtractZip = oldExtract
|
|
})
|
|
fail := errors.New("failure")
|
|
cmd := skillCoverageCommand()
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "", fail }
|
|
if err := runSkillGet(cmd, nil); !errors.Is(err, fail) {
|
|
t.Fatalf("skill get auth error = %v", err)
|
|
}
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "token", nil }
|
|
skillNewRequest = func(context.Context, string, string, io.Reader) (*http.Request, error) { return nil, fail }
|
|
if err := runSkillFind(cmd, nil); err == nil {
|
|
t.Fatal("skill find request failure should propagate")
|
|
}
|
|
skillNewRequest = oldNewRequest
|
|
skillDownloadToTmp = func(context.Context, string, string) (string, error) { return "", fail }
|
|
if err := runSkillGet(cmd, nil); !errors.Is(err, fail) {
|
|
t.Fatalf("skill get download error = %v", err)
|
|
}
|
|
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "", fail }
|
|
if err := runSkillFind(cmd, nil); !errors.Is(err, fail) {
|
|
t.Fatalf("skill find auth error = %v", err)
|
|
}
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "token", nil }
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) { return nil, fail }
|
|
if err := runSkillFind(cmd, nil); err == nil {
|
|
t.Fatal("skill find network failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusBadRequest, "bad"), nil
|
|
}
|
|
if err := runSkillFind(cmd, nil); err == nil {
|
|
t.Fatal("skill find HTTP failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, "{"), nil
|
|
}
|
|
if err := runSkillFind(cmd, nil); err == nil {
|
|
t.Fatal("skill find malformed response should fail")
|
|
}
|
|
for _, body := range []string{
|
|
`{"success":false,"errorMsg":"message"}`,
|
|
`{"success":false,"errorCode":"code"}`,
|
|
`{"success":false}`,
|
|
} {
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, body), nil
|
|
}
|
|
if err := runSkillFind(cmd, nil); err == nil {
|
|
t.Fatalf("skill find API failure %s should propagate", body)
|
|
}
|
|
}
|
|
|
|
skillResolveTargetPath = func(string) (string, error) { return "", fail }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); err == nil {
|
|
t.Fatal("invalid skill target should fail")
|
|
}
|
|
skillResolveTargetPath = func(string) (string, error) { return filepath.Join(t.TempDir(), "dest"), nil }
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "", fail }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); !errors.Is(err, fail) {
|
|
t.Fatalf("skill add auth error = %v", err)
|
|
}
|
|
skillLoadAccessToken = func(context.Context) (string, error) { return "token", nil }
|
|
skillFetchDownloadInfo = func(context.Context, string, string) (*downloadSkillResponse, error) { return nil, fail }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); !errors.Is(err, fail) {
|
|
t.Fatalf("skill info error = %v", err)
|
|
}
|
|
for _, response := range []*downloadSkillResponse{
|
|
{Success: false, ErrorMsg: "message"},
|
|
{Success: false, ErrorCode: "code"},
|
|
{Success: false},
|
|
{Success: true},
|
|
{Success: true, Result: &downloadSkillResult{}},
|
|
} {
|
|
skillFetchDownloadInfo = func(context.Context, string, string) (*downloadSkillResponse, error) { return response, nil }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); err == nil {
|
|
t.Fatalf("invalid download response %#v should fail", response)
|
|
}
|
|
}
|
|
skillFetchDownloadInfo = func(context.Context, string, string) (*downloadSkillResponse, error) {
|
|
return &downloadSkillResponse{Success: true, Result: &downloadSkillResult{DownloadURL: "url", FileName: "skill.zip"}}, nil
|
|
}
|
|
skillDownloadFile = func(context.Context, string, string) (string, error) { return "", fail }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); !errors.Is(err, fail) {
|
|
t.Fatalf("skill file download error = %v", err)
|
|
}
|
|
skillDownloadFile = func(context.Context, string, string) (string, error) { return "temp.zip", nil }
|
|
skillExtractZip = func(string, string) error { return fail }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); !errors.Is(err, fail) {
|
|
t.Fatalf("skill extraction error = %v", err)
|
|
}
|
|
skillExtractZip = func(string, string) error { return nil }
|
|
if err := runSkillAdd(cmd, []string{"id", "target"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestCrossPlatformCoverageSkillCommandLowLevelRemainingCoverage(t *testing.T) {
|
|
oldHTTP := skillHTTPDo
|
|
oldNewRequest, oldResolveToken := skillNewRequest, skillResolveAccessToken
|
|
oldHome := skillUserHomeDir
|
|
oldMkdirTemp, oldCreate, oldCreateTemp := skillMkdirTemp, skillCreate, skillCreateTemp
|
|
oldRemoveAll, oldRemove, oldMkdir := skillRemoveAll, skillRemove, skillMkdirAll
|
|
oldOpen, oldCopy, oldZipOpen := skillOpenFile, skillCopy, skillOpenZipFile
|
|
t.Cleanup(func() {
|
|
skillHTTPDo = oldHTTP
|
|
skillNewRequest, skillResolveAccessToken = oldNewRequest, oldResolveToken
|
|
skillUserHomeDir = oldHome
|
|
skillMkdirTemp, skillCreate, skillCreateTemp = oldMkdirTemp, oldCreate, oldCreateTemp
|
|
skillRemoveAll, skillRemove, skillMkdirAll = oldRemoveAll, oldRemove, oldMkdir
|
|
skillOpenFile, skillCopy, skillOpenZipFile = oldOpen, oldCopy, oldZipOpen
|
|
})
|
|
fail := errors.New("failure")
|
|
skillResolveAccessToken = func(context.Context, string, string) (string, error) {
|
|
return "", authpkg.ErrTokenDataNotFound
|
|
}
|
|
if _, err := loadSkillAccessToken(context.Background()); err == nil {
|
|
t.Fatal("invalid skill access token succeeded")
|
|
}
|
|
canceled, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
skillResolveAccessToken = func(ctx context.Context, _, _ string) (string, error) {
|
|
return "", ctx.Err()
|
|
}
|
|
if _, err := loadSkillAccessToken(canceled); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("skill token cancellation = %v", err)
|
|
}
|
|
skillResolveAccessToken = oldResolveToken
|
|
skillNewRequest = func(context.Context, string, string, io.Reader) (*http.Request, error) { return nil, fail }
|
|
if _, err := fetchSkillDownloadInfo(context.Background(), "token", "id"); err == nil {
|
|
t.Fatal("download-info request failure should propagate")
|
|
}
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "token"); err == nil {
|
|
t.Fatal("skill-file request failure should propagate")
|
|
}
|
|
skillNewRequest = oldNewRequest
|
|
skillUserHomeDir = func() (string, error) { return "", fail }
|
|
if _, err := resolveSkillTargetPath("codex"); err == nil {
|
|
t.Fatal("skill target HOME error should fail")
|
|
}
|
|
t.Setenv("DWS_SKILL_API_HOST", "https://skill.test/")
|
|
if skillAPIHost() != "https://skill.test" {
|
|
t.Fatal("skill API override was not normalized")
|
|
}
|
|
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) { return nil, fail }
|
|
if _, err := fetchSkillDownloadInfo(context.Background(), "token", "id"); err == nil {
|
|
t.Fatal("download-info network failure should propagate")
|
|
}
|
|
for _, status := range []int{http.StatusUnauthorized, http.StatusBadGateway} {
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(status, "body"), nil
|
|
}
|
|
if _, err := fetchSkillDownloadInfo(context.Background(), "token", "id"); err == nil {
|
|
t.Fatalf("download-info HTTP %d should fail", status)
|
|
}
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return &http.Response{StatusCode: http.StatusOK, Body: skillErrorReadCloser{err: fail}}, nil
|
|
}
|
|
if _, err := fetchSkillDownloadInfo(context.Background(), "token", "id"); err == nil {
|
|
t.Fatal("download-info body failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, "{"), nil
|
|
}
|
|
if _, err := fetchSkillDownloadInfo(context.Background(), "token", "id"); err == nil {
|
|
t.Fatal("malformed download-info should fail")
|
|
}
|
|
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, "body"), nil
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) { return nil, fail }
|
|
if _, err := downloadSkillToTmpDir(context.Background(), "https://skill.test", "token"); err == nil {
|
|
t.Fatal("temporary skill download network failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, "body"), nil
|
|
}
|
|
skillMkdirTemp = func(string, string) (string, error) { return "", fail }
|
|
if _, err := downloadSkillToTmpDir(context.Background(), "https://skill.test", "token"); err == nil {
|
|
t.Fatal("download temp-dir failure should propagate")
|
|
}
|
|
tmpDir := t.TempDir()
|
|
skillMkdirTemp = func(string, string) (string, error) { return tmpDir, nil }
|
|
skillCreate = func(string) (*os.File, error) { return nil, fail }
|
|
if _, err := downloadSkillToTmpDir(context.Background(), "https://skill.test", "token"); err == nil {
|
|
t.Fatal("download temp-file failure should propagate")
|
|
}
|
|
skillCreate = func(string) (*os.File, error) { return os.CreateTemp(t.TempDir(), "skill") }
|
|
skillCopy = func(io.Writer, io.Reader) (int64, error) { return 0, fail }
|
|
if _, err := downloadSkillToTmpDir(context.Background(), "https://skill.test", "token"); err == nil {
|
|
t.Fatal("download save failure should propagate")
|
|
}
|
|
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) { return nil, fail }
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "x"); err == nil {
|
|
t.Fatal("skill-file network failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusBadGateway, "body"), nil
|
|
}
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "x"); err == nil {
|
|
t.Fatal("skill-file HTTP failure should propagate")
|
|
}
|
|
skillHTTPDo = func(*http.Client, *http.Request) (*http.Response, error) {
|
|
return skillCoverageResponse(http.StatusOK, "body"), nil
|
|
}
|
|
skillCreateTemp = func(string, string) (*os.File, error) { return nil, fail }
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "x"); err == nil {
|
|
t.Fatal("skill-file temp failure should propagate")
|
|
}
|
|
skillCreateTemp = func(string, string) (*os.File, error) { return os.CreateTemp(t.TempDir(), "skill") }
|
|
skillCopy = func(io.Writer, io.Reader) (int64, error) { return 0, fail }
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "x"); err == nil {
|
|
t.Fatal("skill-file copy failure should propagate")
|
|
}
|
|
skillCopy = func(w io.Writer, _ io.Reader) (int64, error) {
|
|
_ = w.(*os.File).Close()
|
|
return 0, nil
|
|
}
|
|
if _, err := downloadSkillFile(context.Background(), "https://skill.test", "x"); err == nil {
|
|
t.Fatal("skill-file close failure should propagate")
|
|
}
|
|
|
|
skillMkdirAll = func(string, os.FileMode) error { return fail }
|
|
if err := extractSkillZip("missing", filepath.Join(t.TempDir(), "dest")); err == nil {
|
|
t.Fatal("zip destination mkdir failure should propagate")
|
|
}
|
|
zipPath := filepath.Join(t.TempDir(), "files.zip")
|
|
buf := &bytes.Buffer{}
|
|
zw := zip.NewWriter(buf)
|
|
_, _ = zw.Create("dir/")
|
|
w, _ := zw.Create("file")
|
|
_, _ = w.Write([]byte("content"))
|
|
header := &zip.FileHeader{Name: "mode-file", Method: zip.Store}
|
|
header.SetMode(0o111)
|
|
w, _ = zw.CreateHeader(header)
|
|
_, _ = w.Write([]byte("mode"))
|
|
_ = zw.Close()
|
|
if err := os.WriteFile(zipPath, buf.Bytes(), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
zr, err := zip.OpenReader(zipPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer zr.Close()
|
|
skillMkdirAll = func(string, os.FileMode) error { return fail }
|
|
if err := extractZipFile(zr.File[0], t.TempDir()); err == nil {
|
|
t.Fatal("zip directory mkdir failure should propagate")
|
|
}
|
|
if err := extractZipFile(zr.File[1], t.TempDir()); err == nil {
|
|
t.Fatal("zip parent mkdir failure should propagate")
|
|
}
|
|
skillMkdirAll = func(string, os.FileMode) error { return nil }
|
|
skillOpenZipFile = func(*zip.File) (io.ReadCloser, error) { return nil, fail }
|
|
if err := extractZipFile(zr.File[1], t.TempDir()); err == nil {
|
|
t.Fatal("zip source-open failure should propagate")
|
|
}
|
|
skillOpenZipFile = oldZipOpen
|
|
skillOpenFile = func(string, int, os.FileMode) (*os.File, error) { return nil, fail }
|
|
if err := extractZipFile(zr.File[1], t.TempDir()); err == nil {
|
|
t.Fatal("zip destination-open failure should propagate")
|
|
}
|
|
skillOpenFile = func(string, int, os.FileMode) (*os.File, error) { return os.CreateTemp(t.TempDir(), "out") }
|
|
skillCopy = func(io.Writer, io.Reader) (int64, error) { return 0, fail }
|
|
if err := extractZipFile(zr.File[1], t.TempDir()); err == nil {
|
|
t.Fatal("zip copy failure should propagate")
|
|
}
|
|
skillCopy = func(io.Writer, io.Reader) (int64, error) { return 0, nil }
|
|
var usedMode os.FileMode
|
|
skillOpenFile = func(_ string, _ int, mode os.FileMode) (*os.File, error) {
|
|
usedMode = mode
|
|
return os.CreateTemp(t.TempDir(), "mode")
|
|
}
|
|
if err := extractZipFile(zr.File[2], t.TempDir()); err != nil || usedMode != 0o644 {
|
|
t.Fatalf("zip fallback mode = %v, %v", usedMode, err)
|
|
}
|
|
}
|