diff --git a/shortcuts/apps/apps_html_publish_test.go b/shortcuts/apps/apps_html_publish_test.go index 0e13646bc..9be7f1d8d 100644 --- a/shortcuts/apps/apps_html_publish_test.go +++ b/shortcuts/apps/apps_html_publish_test.go @@ -6,10 +6,21 @@ package apps import ( "context" "errors" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" "testing" + + "github.com/spf13/cobra" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/extension/fileio" + "github.com/larksuite/cli/internal/cmdutil" + "github.com/larksuite/cli/internal/core" + "github.com/larksuite/cli/internal/httpmock" + "github.com/larksuite/cli/shortcuts/common" ) type fakeAppsHTMLPublishClient struct { @@ -582,3 +593,216 @@ func TestRunHTMLPublish_IgnoresOversizeNonHTML(t *testing.T) { t.Fatalf("client should be called; calls=%v", fake.calls) } } + +// ── runHTMLPublishTOS tests ── + +// permissiveFIOProvider wraps permissiveFIO as a fileio.Provider for tests +// that call runHTMLPublishTOS (which obtains FileIO via rctx.FileIO()). +type permissiveFIOProvider struct{} + +func (permissiveFIOProvider) Name() string { return "test-permissive" } +func (permissiveFIOProvider) ResolveFileIO(context.Context) fileio.FileIO { return permissiveFIO{} } + +// newTOSTestRuntime builds a RuntimeContext with httpmock registry and a +// permissive FileIO provider, ready for runHTMLPublishTOS unit tests. +func newTOSTestRuntime(t *testing.T) (*common.RuntimeContext, *httpmock.Registry) { + t.Helper() + t.Setenv("HOME", t.TempDir()) + t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) + cfg := &core.CliConfig{ + AppID: "test-app-" + strings.ToLower(t.Name()), + AppSecret: "test-secret", + Brand: core.BrandFeishu, + UserOpenId: "ou_test", + } + factory, _, _, reg := cmdutil.TestFactory(t, cfg) + factory.FileIOProvider = permissiveFIOProvider{} + rt := common.TestNewRuntimeContextForAPI( + context.Background(), + &cobra.Command{Use: "+tos-test"}, + cfg, factory, core.AsUser, + ) + return rt, reg +} + +func TestRunHTMLPublishTOS_Success(t *testing.T) { + site := writeAppsSampleSite(t) + rt, reg := newTOSTestRuntime(t) + + // Start httptest server to accept the TOS upload. + tosServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Errorf("TOS upload method = %s, want POST", r.Method) + } + if ct := r.Header.Get("Content-Type"); ct != "application/gzip" { + t.Errorf("TOS upload Content-Type = %s, want application/gzip", ct) + } + w.WriteHeader(http.StatusOK) + })) + defer tosServer.Close() + + // Register pre_release API stub. + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/spark/v1/apps/app_tos/pre_release", + Body: map[string]interface{}{ + "code": float64(0), + "data": map[string]interface{}{ + "params": map[string]interface{}{ + "upload_url": tosServer.URL, + "tos_path": "tos://bucket/key", + }, + }, + }, + }) + + out, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: site, + }) + if err != nil { + t.Fatalf("err=%v", err) + } + if out["app_id"] != "app_tos" { + t.Fatalf("app_id=%v, want app_tos", out["app_id"]) + } + if out["tos_path"] != "tos://bucket/key" { + t.Fatalf("tos_path=%v, want tos://bucket/key", out["tos_path"]) + } +} + +func TestRunHTMLPublishTOS_MissingIndexHTML(t *testing.T) { + dir := t.TempDir() + // Create a file that is NOT named index.html. + if err := os.WriteFile(filepath.Join(dir, "foo.html"), []byte(""), 0o644); err != nil { + t.Fatalf("write: %v", err) + } + + rt, _ := newTOSTestRuntime(t) + _, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: dir, + }) + if err == nil { + t.Fatalf("expected error for missing index.html") + } + if !strings.Contains(err.Error(), "index.html") { + t.Fatalf("error should mention index.html, got: %v", err) + } +} + +func TestRunHTMLPublishTOS_PreReleaseError(t *testing.T) { + site := writeAppsSampleSite(t) + rt, reg := newTOSTestRuntime(t) + + // Register pre_release API stub that returns an error code. + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/spark/v1/apps/app_tos/pre_release", + Body: map[string]interface{}{ + "code": float64(99999), + "msg": "internal server error", + }, + }) + + _, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: site, + }) + if err == nil { + t.Fatalf("expected error from pre_release API failure") + } +} + +func TestRunHTMLPublishTOS_MissingParams(t *testing.T) { + site := writeAppsSampleSite(t) + rt, reg := newTOSTestRuntime(t) + + // Register pre_release API stub that returns empty params. + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/spark/v1/apps/app_tos/pre_release", + Body: map[string]interface{}{ + "code": float64(0), + "data": map[string]interface{}{ + "params": map[string]interface{}{}, + }, + }, + }) + + _, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: site, + }) + if err == nil { + t.Fatalf("expected error for missing upload_url/tos_path") + } + problem := requireAppsProblem(t, err, errs.CategoryInternal) + if !strings.Contains(problem.Message, "upload_url") || !strings.Contains(problem.Message, "tos_path") { + t.Fatalf("error should mention missing upload_url or tos_path, got: %q", problem.Message) + } +} + +func TestRunHTMLPublishTOS_MissingParamsObject(t *testing.T) { + site := writeAppsSampleSite(t) + rt, reg := newTOSTestRuntime(t) + + // Register pre_release API stub that returns no params key at all. + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/spark/v1/apps/app_tos/pre_release", + Body: map[string]interface{}{ + "code": float64(0), + "data": map[string]interface{}{}, + }, + }) + + _, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: site, + }) + if err == nil { + t.Fatalf("expected error for missing params object") + } + problem := requireAppsProblem(t, err, errs.CategoryInternal) + if !strings.Contains(problem.Message, "no params") { + t.Fatalf("error should mention 'no params', got: %q", problem.Message) + } +} + +func TestRunHTMLPublishTOS_UploadFails(t *testing.T) { + site := writeAppsSampleSite(t) + rt, reg := newTOSTestRuntime(t) + + // Start httptest server that returns 500. + tosServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + })) + defer tosServer.Close() + + reg.Register(&httpmock.Stub{ + Method: "POST", + URL: "/open-apis/spark/v1/apps/app_tos/pre_release", + Body: map[string]interface{}{ + "code": float64(0), + "data": map[string]interface{}{ + "params": map[string]interface{}{ + "upload_url": tosServer.URL, + "tos_path": "tos://bucket/key", + }, + }, + }, + }) + + _, err := runHTMLPublishTOS(context.Background(), rt, appsHTMLPublishSpec{ + AppID: "app_tos", + Path: site, + }) + if err == nil { + t.Fatalf("expected error from TOS upload failure") + } + problem := requireAppsProblem(t, err, errs.CategoryNetwork) + if !strings.Contains(problem.Message, "500") { + t.Fatalf("error should mention HTTP 500, got: %q", problem.Message) + } +}