mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 17:45:15 +08:00
refactor(apps): replace appMeta struct with queryAppType string for simpler routing
This commit is contained in:
@@ -120,10 +120,10 @@ var AppsHTMLPublish = common.Shortcut{
|
||||
Path: strings.TrimSpace(rctx.Str("path")),
|
||||
}
|
||||
|
||||
meta := queryAppMeta(ctx, rctx, spec.AppID)
|
||||
appType := queryAppType(ctx, rctx, spec.AppID)
|
||||
|
||||
// doubao-html (app_type=7, arch_type=4): zip + TOS path
|
||||
if meta != nil && meta.AppType == 7 && meta.ArchType == 4 {
|
||||
// doubao-html (modern_html): zip + TOS path
|
||||
if appType == "modern_html" {
|
||||
tosClient := appsHTMLPublishTOSAPI{runtime: rctx}
|
||||
out, err := runHTMLPublishTOS(ctx, rctx.FileIO(), tosClient, rctx, spec)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -87,7 +87,7 @@ var AppsInit = common.Shortcut{
|
||||
},
|
||||
DryRun: func(ctx context.Context, rctx *common.RuntimeContext) *common.DryRunAPI {
|
||||
appID := strings.TrimSpace(rctx.Str("app-id"))
|
||||
template := resolveTemplate(rctx, nil)
|
||||
template := resolveTemplate(rctx, "")
|
||||
dry := common.NewDryRunAPI().
|
||||
Desc("Initialize app code (credential-init, clone, checkout, npx code-init, optional commit/push)").
|
||||
Set("credential_init", fmt.Sprintf("apps +git-credential-init --app-id %s --format json", appID)).
|
||||
@@ -125,19 +125,17 @@ func defaultCloneDir(appID string) string {
|
||||
}
|
||||
|
||||
// resolveTemplate returns the scaffold template for an empty-repo `app init`.
|
||||
// An explicit --template wins. When meta is available the template is derived
|
||||
// from app_type/arch_type; otherwise it falls back to defaultTemplate.
|
||||
func resolveTemplate(rctx *common.RuntimeContext, meta *appMeta) string {
|
||||
// An explicit --template wins. When appType is available the template is derived
|
||||
// from it; otherwise it falls back to defaultTemplate.
|
||||
func resolveTemplate(rctx *common.RuntimeContext, appType string) string {
|
||||
if t := strings.TrimSpace(rctx.Str("template")); t != "" {
|
||||
return t
|
||||
}
|
||||
if meta != nil {
|
||||
switch {
|
||||
case meta.AppType == 7 && meta.ArchType == 3:
|
||||
return ""
|
||||
case meta.AppType == 7 && meta.ArchType == 4:
|
||||
return "vite-react"
|
||||
}
|
||||
if appType == "html" {
|
||||
return ""
|
||||
}
|
||||
if appType != "" {
|
||||
return appType
|
||||
}
|
||||
return defaultTemplate
|
||||
}
|
||||
@@ -332,8 +330,8 @@ func isEmptyRepo(ctx context.Context, dir string) (bool, error) {
|
||||
// runScaffold runs the npx scaffolding step inside the cloned repo (cwd=dir).
|
||||
// Empty repo -> `app init`; non-empty -> `app sync` + meta app_id patch +
|
||||
// conditional `skills sync`. Returns "init", "upgrade", or "skipped".
|
||||
func runScaffold(ctx context.Context, dir, appID string, meta *appMeta, explicitTemplate string) (string, error) {
|
||||
if isStaticHtml(meta) {
|
||||
func runScaffold(ctx context.Context, dir, appID, appType, explicitTemplate string) (string, error) {
|
||||
if appType == "html" {
|
||||
return scaffoldKindSkipped, nil
|
||||
}
|
||||
empty, err := isEmptyRepo(ctx, dir)
|
||||
@@ -341,7 +339,7 @@ func runScaffold(ctx context.Context, dir, appID string, meta *appMeta, explicit
|
||||
return "", err
|
||||
}
|
||||
if empty {
|
||||
args := scaffoldInitArgs(meta, appID, explicitTemplate)
|
||||
args := scaffoldInitArgs(appType, appID, explicitTemplate)
|
||||
if _, stderr, err := initRunner.Run(ctx, dir, "npx", args...); err != nil {
|
||||
return "", appsExternalToolError(err, "npx app init failed: %s", gitErr(stderr, err))
|
||||
}
|
||||
@@ -362,19 +360,15 @@ func runScaffold(ctx context.Context, dir, appID string, meta *appMeta, explicit
|
||||
}
|
||||
|
||||
// scaffoldInitArgs builds the npx argument list for `app init`. An explicit
|
||||
// template wins; otherwise, when meta is available, --app-type/--arch-type are
|
||||
// passed so miaoda-cli picks the right scaffold; nil meta falls back to
|
||||
// --template defaultTemplate.
|
||||
func scaffoldInitArgs(meta *appMeta, appID, explicitTemplate string) []string {
|
||||
// template wins; otherwise, when appType is available, it is passed as
|
||||
// --template; empty appType falls back to --template defaultTemplate.
|
||||
func scaffoldInitArgs(appType, appID, explicitTemplate string) []string {
|
||||
base := []string{"-y", "--prefer-online", miaodaCLIPkg, "app", "init"}
|
||||
if explicitTemplate != "" {
|
||||
return append(base, "--template", explicitTemplate, "--app-id", appID)
|
||||
}
|
||||
if meta != nil {
|
||||
return append(base,
|
||||
"--app-type", strconv.Itoa(meta.AppType),
|
||||
"--arch-type", strconv.Itoa(meta.ArchType),
|
||||
"--app-id", appID)
|
||||
if appType != "" {
|
||||
return append(base, "--template", appType, "--app-id", appID)
|
||||
}
|
||||
return append(base, "--template", defaultTemplate, "--app-id", appID)
|
||||
}
|
||||
@@ -470,7 +464,7 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
return err
|
||||
}
|
||||
|
||||
meta := queryAppMeta(ctx, rctx, appID)
|
||||
appType := queryAppType(ctx, rctx, appID)
|
||||
|
||||
// Already-initialized short-circuit: a dir containing .spark/meta.json is an
|
||||
// initialized app repo -> skip clone/scaffold/commit, but still refresh
|
||||
@@ -485,11 +479,10 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
"committed": false,
|
||||
"pushed": false,
|
||||
}
|
||||
if meta != nil {
|
||||
out["app_type"] = meta.AppType
|
||||
out["arch_type"] = meta.ArchType
|
||||
if appType != "" {
|
||||
out["app_type"] = appType
|
||||
}
|
||||
if isStaticHtml(meta) {
|
||||
if appType == "html" {
|
||||
out["env_pulled"] = false
|
||||
out["env_pull_skipped"] = true
|
||||
out["message"] = "Repository already initialized (static HTML app — no env pull needed)."
|
||||
@@ -529,7 +522,7 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
return appsFailedPreconditionError("git executable not found on PATH").
|
||||
WithHint("install git and ensure it is on your PATH")
|
||||
}
|
||||
if !isStaticHtml(meta) {
|
||||
if appType != "html" {
|
||||
if _, err := exec.LookPath("npx"); err != nil {
|
||||
return appsFailedPreconditionError("npx executable not found on PATH").
|
||||
WithHint("install Node.js (which provides npx) and ensure it is on your PATH")
|
||||
@@ -560,7 +553,7 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
|
||||
initLogf(rctx, "Initializing app code (running miaoda-cli)...")
|
||||
explicitTemplate := strings.TrimSpace(rctx.Str("template"))
|
||||
scaffold, err := runScaffold(ctx, dir, appID, meta, explicitTemplate)
|
||||
scaffold, err := runScaffold(ctx, dir, appID, appType, explicitTemplate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -585,14 +578,13 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
"pushed": pushed,
|
||||
"message": "Repository initialized. You can start developing.",
|
||||
}
|
||||
if meta != nil {
|
||||
out["app_type"] = meta.AppType
|
||||
out["arch_type"] = meta.ArchType
|
||||
if appType != "" {
|
||||
out["app_type"] = appType
|
||||
}
|
||||
|
||||
var envFile string
|
||||
var envPulled bool
|
||||
if isStaticHtml(meta) {
|
||||
if appType == "html" {
|
||||
out["env_pulled"] = false
|
||||
out["env_pull_skipped"] = true
|
||||
} else {
|
||||
@@ -614,7 +606,7 @@ func appsInitExecute(ctx context.Context, rctx *common.RuntimeContext) error {
|
||||
rctx.OutFormat(out, nil, func(w io.Writer) {
|
||||
fmt.Fprintf(w, "✓ Repository initialized at %s\n", dir)
|
||||
fmt.Fprintf(w, " branch: %s\n scaffold: %s\n", defaultInitBranch, scaffold)
|
||||
if isStaticHtml(meta) {
|
||||
if appType == "html" {
|
||||
fmt.Fprintln(w, " (static HTML app — env pull skipped)")
|
||||
} else if envPulled {
|
||||
fmt.Fprintf(w, "✓ Local environment written to %s\n", envFile)
|
||||
|
||||
@@ -48,14 +48,14 @@ func testRuntimeWithTemplate(t *testing.T, dirFlag, tpl string) *common.RuntimeC
|
||||
}
|
||||
|
||||
func TestResolveTemplate(t *testing.T) {
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", "foo"), nil); got != "foo" {
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", "foo"), ""); got != "foo" {
|
||||
t.Errorf("explicit --template = %q, want foo", got)
|
||||
}
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", ""), nil); got != defaultTemplate {
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", ""), ""); got != defaultTemplate {
|
||||
t.Errorf("omitted --template = %q, want fallback %q", got, defaultTemplate)
|
||||
}
|
||||
// Whitespace-only --template is treated as omitted -> fallback.
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", " "), nil); got != defaultTemplate {
|
||||
if got := resolveTemplate(testRuntimeWithTemplate(t, "", " "), ""); got != defaultTemplate {
|
||||
t.Errorf("whitespace --template = %q, want fallback %q", got, defaultTemplate)
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,7 @@ func TestRunScaffold_EmptyRepo(t *testing.T) {
|
||||
t.Run("ls="+ls, func(t *testing.T) {
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: ls}}}
|
||||
withFakeRunner(t, f)
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", nil, "nestjs-react-fullstack")
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", "", "nestjs-react-fullstack")
|
||||
if err != nil || kind != "init" {
|
||||
t.Fatalf("ls=%q kind=%q err=%v, want init", ls, kind, err)
|
||||
}
|
||||
@@ -280,7 +280,7 @@ func TestRunScaffold_NonEmpty_SyncsWhenNoSteering(t *testing.T) {
|
||||
dir := t.TempDir() // no steering dir, no meta.json
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: "src/x.ts\n"}}}
|
||||
withFakeRunner(t, f)
|
||||
kind, err := runScaffold(context.Background(), dir, "app_x", nil, "nestjs-react-fullstack")
|
||||
kind, err := runScaffold(context.Background(), dir, "app_x", "", "nestjs-react-fullstack")
|
||||
if err != nil || kind != "upgrade" {
|
||||
t.Fatalf("kind=%q err=%v, want upgrade", kind, err)
|
||||
}
|
||||
@@ -299,7 +299,7 @@ func TestRunScaffold_NonEmpty_SkipsSyncWhenSteeringExists(t *testing.T) {
|
||||
os.MkdirAll(filepath.Join(dir, steeringRelPath), 0o755)
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: "src/x.ts\n"}}}
|
||||
withFakeRunner(t, f)
|
||||
if _, err := runScaffold(context.Background(), dir, "app_x", nil, "nestjs-react-fullstack"); err != nil {
|
||||
if _, err := runScaffold(context.Background(), dir, "app_x", "", "nestjs-react-fullstack"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if findCallArg(f.calls, "npx", "skills", "sync") != nil {
|
||||
@@ -313,7 +313,7 @@ func TestRunScaffold_AppInitFailure(t *testing.T) {
|
||||
"npx -y": {stderr: "boom", err: errors.New("exit 1")},
|
||||
}}
|
||||
withFakeRunner(t, f)
|
||||
if _, err := runScaffold(context.Background(), t.TempDir(), "app_x", nil, "nestjs-react-fullstack"); err == nil {
|
||||
if _, err := runScaffold(context.Background(), t.TempDir(), "app_x", "", "nestjs-react-fullstack"); err == nil {
|
||||
t.Error("app init failure must propagate")
|
||||
}
|
||||
}
|
||||
@@ -1250,7 +1250,7 @@ func TestRunScaffold_NonEmpty_SyncFailure(t *testing.T) {
|
||||
"git ls-files": {stdout: "src/x.ts\n"},
|
||||
"npx -y": {err: errors.New("sync boom")},
|
||||
}})
|
||||
if _, err := runScaffold(context.Background(), t.TempDir(), "app_x", nil, "tpl"); err == nil {
|
||||
if _, err := runScaffold(context.Background(), t.TempDir(), "app_x", "", "tpl"); err == nil {
|
||||
t.Error("npx app sync failure must surface as an error")
|
||||
}
|
||||
}
|
||||
@@ -1630,7 +1630,7 @@ func TestRunScaffold_SubprocessFailureIsExternalTool(t *testing.T) {
|
||||
"git ls-files": {stderr: "fatal: not a git repository", err: cause},
|
||||
}}
|
||||
withFakeRunner(t, f)
|
||||
_, err := runScaffold(context.Background(), t.TempDir(), "app_x", nil, "nestjs-react-fullstack")
|
||||
_, err := runScaffold(context.Background(), t.TempDir(), "app_x", "", "nestjs-react-fullstack")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error from failing git subprocess")
|
||||
}
|
||||
@@ -1649,8 +1649,7 @@ func TestRunScaffold_SubprocessFailureIsExternalTool(t *testing.T) {
|
||||
func TestRunScaffold_StaticHtmlSkipped(t *testing.T) {
|
||||
f := &fakeCommandRunner{}
|
||||
withFakeRunner(t, f)
|
||||
meta := &appMeta{AppType: 7, ArchType: 3}
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", meta, "")
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", "html", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -1662,11 +1661,10 @@ func TestRunScaffold_StaticHtmlSkipped(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunScaffold_DoubaoHtmlPassesTypes(t *testing.T) {
|
||||
func TestRunScaffold_ModernHtmlPassesTemplate(t *testing.T) {
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: ""}}}
|
||||
withFakeRunner(t, f)
|
||||
meta := &appMeta{AppType: 7, ArchType: 4}
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", meta, "")
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", "modern_html", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -1677,18 +1675,15 @@ func TestRunScaffold_DoubaoHtmlPassesTypes(t *testing.T) {
|
||||
if c == nil {
|
||||
t.Fatal("npx not called")
|
||||
}
|
||||
if !containsAll(c, "--app-type", "7", "--arch-type", "4") {
|
||||
t.Errorf("expected --app-type 7 --arch-type 4 in args: %v", c)
|
||||
}
|
||||
if containsAll(c, "--template") {
|
||||
t.Errorf("should not contain --template when meta is present: %v", c)
|
||||
if !containsAll(c, "--template", "modern_html") {
|
||||
t.Errorf("expected --template modern_html in args: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunScaffold_NilMetaFallback(t *testing.T) {
|
||||
func TestRunScaffold_EmptyAppTypeFallback(t *testing.T) {
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: ""}}}
|
||||
withFakeRunner(t, f)
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", nil, "")
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -1707,8 +1702,7 @@ func TestRunScaffold_NilMetaFallback(t *testing.T) {
|
||||
func TestRunScaffold_ExplicitTemplateOverride(t *testing.T) {
|
||||
f := &fakeCommandRunner{results: map[string]fakeCallResult{"git ls-files": {stdout: ""}}}
|
||||
withFakeRunner(t, f)
|
||||
meta := &appMeta{AppType: 7, ArchType: 4}
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", meta, "custom-template")
|
||||
kind, err := runScaffold(context.Background(), t.TempDir(), "app_x", "modern_html", "custom-template")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -1722,7 +1716,4 @@ func TestRunScaffold_ExplicitTemplateOverride(t *testing.T) {
|
||||
if !containsAll(c, "--template", "custom-template") {
|
||||
t.Errorf("expected --template custom-template in args: %v", c)
|
||||
}
|
||||
if containsAll(c, "--app-type") {
|
||||
t.Errorf("--template should override --app-type: %v", c)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,61 +5,21 @@ package apps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// appMeta holds the numeric app_type and arch_type returned by the server.
|
||||
type appMeta struct {
|
||||
AppType int
|
||||
ArchType int
|
||||
}
|
||||
|
||||
// queryAppMeta fetches the app's metadata (numeric app_type and arch_type)
|
||||
// from the server. Returns nil when the API is unavailable or returns an
|
||||
// error — callers fall back to legacy behavior.
|
||||
func queryAppMeta(ctx context.Context, rctx *common.RuntimeContext, appID string) *appMeta {
|
||||
path := fmt.Sprintf("%s/apps/%s", apiBasePath, validate.EncodePathSegment(appID))
|
||||
// queryAppType fetches the app's type string ("html", "modern_html",
|
||||
// "full_stack", etc.) from the server. Returns "" when the API is unavailable
|
||||
// or returns an error — callers fall back to legacy behavior.
|
||||
func queryAppType(ctx context.Context, rctx *common.RuntimeContext, appID string) string {
|
||||
path := fmt.Sprintf("%s/apps/%s/type", apiBasePath, validate.EncodePathSegment(appID))
|
||||
data, err := rctx.CallAPITyped("GET", path, nil, nil)
|
||||
if err != nil {
|
||||
return nil
|
||||
return ""
|
||||
}
|
||||
app, _ := data["app"].(map[string]interface{})
|
||||
if app == nil {
|
||||
return nil
|
||||
}
|
||||
appType, ok1 := toInt(app["app_type"])
|
||||
archType, ok2 := toInt(app["arch_type"])
|
||||
if !ok1 || !ok2 {
|
||||
return nil
|
||||
}
|
||||
return &appMeta{AppType: appType, ArchType: archType}
|
||||
}
|
||||
|
||||
// toInt converts a JSON number (float64 or json.Number) to int.
|
||||
func toInt(v interface{}) (int, bool) {
|
||||
switch n := v.(type) {
|
||||
case float64:
|
||||
return int(n), true
|
||||
case int:
|
||||
return n, true
|
||||
case json.Number:
|
||||
i, err := strconv.Atoi(n.String())
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return i, true
|
||||
default:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
// isStaticHtml reports whether the app is a legacy static-HTML app
|
||||
// (app_type=7, arch_type=3) that has no template and no env vars.
|
||||
func isStaticHtml(meta *appMeta) bool {
|
||||
return meta != nil && meta.AppType == 7 && meta.ArchType == 3
|
||||
appType, _ := data["app_type"].(string)
|
||||
return appType
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package apps
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -17,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
// newMetaTestRuntime creates a RuntimeContext wired to an httpmock.Registry
|
||||
// so queryAppMeta can be tested without a real server.
|
||||
// so queryAppType can be tested without a real server.
|
||||
func newMetaTestRuntime(t *testing.T) (*common.RuntimeContext, *httpmock.Registry) {
|
||||
t.Helper()
|
||||
cfg := &core.CliConfig{Brand: core.BrandFeishu, AppID: "cli_meta_test"}
|
||||
@@ -30,131 +29,53 @@ func newMetaTestRuntime(t *testing.T) (*common.RuntimeContext, *httpmock.Registr
|
||||
return rt, reg
|
||||
}
|
||||
|
||||
func TestQueryAppMeta_Success(t *testing.T) {
|
||||
func TestQueryAppType_Success(t *testing.T) {
|
||||
rt, reg := newMetaTestRuntime(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/spark/v1/apps/app_test123",
|
||||
URL: "/open-apis/spark/v1/apps/app_test/type",
|
||||
Body: map[string]interface{}{
|
||||
"code": float64(0),
|
||||
"data": map[string]interface{}{
|
||||
"app": map[string]interface{}{
|
||||
"app_type": float64(7),
|
||||
"arch_type": float64(4),
|
||||
"name": "Test App",
|
||||
},
|
||||
"app_type": "modern_html",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
meta := queryAppMeta(context.Background(), rt, "app_test123")
|
||||
if meta == nil {
|
||||
t.Fatal("expected non-nil appMeta")
|
||||
}
|
||||
if meta.AppType != 7 {
|
||||
t.Errorf("AppType = %d, want 7", meta.AppType)
|
||||
}
|
||||
if meta.ArchType != 4 {
|
||||
t.Errorf("ArchType = %d, want 4", meta.ArchType)
|
||||
result := queryAppType(context.Background(), rt, "app_test")
|
||||
if result != "modern_html" {
|
||||
t.Errorf("queryAppType = %q, want modern_html", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryAppMeta_APIError(t *testing.T) {
|
||||
func TestQueryAppType_APIError(t *testing.T) {
|
||||
rt, reg := newMetaTestRuntime(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/spark/v1/apps/app_err",
|
||||
Body: map[string]interface{}{
|
||||
"code": float64(500100),
|
||||
"msg": "internal server error",
|
||||
},
|
||||
URL: "/open-apis/spark/v1/apps/app_bad/type",
|
||||
Status: 500,
|
||||
Body: map[string]interface{}{"code": float64(99999), "msg": "internal error"},
|
||||
})
|
||||
|
||||
meta := queryAppMeta(context.Background(), rt, "app_err")
|
||||
if meta != nil {
|
||||
t.Fatalf("expected nil for API error, got %+v", meta)
|
||||
result := queryAppType(context.Background(), rt, "app_bad")
|
||||
if result != "" {
|
||||
t.Errorf("queryAppType = %q, want empty on error", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryAppMeta_MissingFields(t *testing.T) {
|
||||
func TestQueryAppType_MissingField(t *testing.T) {
|
||||
rt, reg := newMetaTestRuntime(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/spark/v1/apps/app_partial",
|
||||
Body: map[string]interface{}{
|
||||
"code": float64(0),
|
||||
"data": map[string]interface{}{
|
||||
"app": map[string]interface{}{
|
||||
"name": "Partial App",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
meta := queryAppMeta(context.Background(), rt, "app_partial")
|
||||
if meta != nil {
|
||||
t.Fatalf("expected nil when app_type/arch_type are missing, got %+v", meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQueryAppMeta_NoAppObject(t *testing.T) {
|
||||
rt, reg := newMetaTestRuntime(t)
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/spark/v1/apps/app_noobj",
|
||||
URL: "/open-apis/spark/v1/apps/app_no/type",
|
||||
Body: map[string]interface{}{
|
||||
"code": float64(0),
|
||||
"data": map[string]interface{}{},
|
||||
},
|
||||
})
|
||||
|
||||
meta := queryAppMeta(context.Background(), rt, "app_noobj")
|
||||
if meta != nil {
|
||||
t.Fatalf("expected nil when app object is absent, got %+v", meta)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsStaticHtml(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
meta *appMeta
|
||||
want bool
|
||||
}{
|
||||
{"nil meta", nil, false},
|
||||
{"static html (7,3)", &appMeta{AppType: 7, ArchType: 3}, true},
|
||||
{"full stack (7,4)", &appMeta{AppType: 7, ArchType: 4}, false},
|
||||
{"other type (3,0)", &appMeta{AppType: 3, ArchType: 0}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isStaticHtml(tt.meta); got != tt.want {
|
||||
t.Errorf("isStaticHtml(%+v) = %v, want %v", tt.meta, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToInt(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
wantN int
|
||||
wantOK bool
|
||||
}{
|
||||
{"float64", float64(42), 42, true},
|
||||
{"int", 7, 7, true},
|
||||
{"json.Number", json.Number("99"), 99, true},
|
||||
{"json.Number non-int", json.Number("abc"), 0, false},
|
||||
{"string", "7", 0, false},
|
||||
{"nil", nil, 0, false},
|
||||
{"bool", true, 0, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
n, ok := toInt(tt.input)
|
||||
if n != tt.wantN || ok != tt.wantOK {
|
||||
t.Errorf("toInt(%v) = (%d, %v), want (%d, %v)", tt.input, n, ok, tt.wantN, tt.wantOK)
|
||||
}
|
||||
})
|
||||
result := queryAppType(context.Background(), rt, "app_no")
|
||||
if result != "" {
|
||||
t.Errorf("queryAppType = %q, want empty when field missing", result)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user