329 lines
10 KiB
Go
329 lines
10 KiB
Go
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
formsapi "google.golang.org/api/forms/v1"
|
|
"google.golang.org/api/option"
|
|
scriptapi "google.golang.org/api/script/v1"
|
|
)
|
|
|
|
func TestExecute_FormsGet_Text(t *testing.T) {
|
|
origNew := newFormsService
|
|
t.Cleanup(func() { newFormsService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !(strings.Contains(r.URL.Path, "/forms/form123") && r.Method == http.MethodGet) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"formId": "form123",
|
|
"responderUri": "https://docs.google.com/forms/d/e/resp",
|
|
"info": map[string]any{
|
|
"title": "Survey",
|
|
"description": "Weekly check-in",
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
svc, err := formsapi.NewService(context.Background(),
|
|
option.WithoutAuthentication(),
|
|
option.WithHTTPClient(srv.Client()),
|
|
option.WithEndpoint(srv.URL+"/"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
newFormsService = func(context.Context, string) (*formsapi.Service, error) { return svc, nil }
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "forms", "get", "form123"}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(out, "id\tform123") || !strings.Contains(out, "title\tSurvey") || !strings.Contains(out, "edit_url\thttps://docs.google.com/forms/d/form123/edit") {
|
|
t.Fatalf("unexpected out=%q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecute_FormsResponsesList_JSON(t *testing.T) {
|
|
origNew := newFormsService
|
|
t.Cleanup(func() { newFormsService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !(strings.Contains(r.URL.Path, "/forms/form123/responses") && r.Method == http.MethodGet) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"responses": []map[string]any{
|
|
{
|
|
"responseId": "r1",
|
|
"lastSubmittedTime": "2026-02-14T00:00:00Z",
|
|
"respondentEmail": "user@example.com",
|
|
},
|
|
},
|
|
"nextPageToken": "next123",
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
svc, err := formsapi.NewService(context.Background(),
|
|
option.WithoutAuthentication(),
|
|
option.WithHTTPClient(srv.Client()),
|
|
option.WithEndpoint(srv.URL+"/"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
newFormsService = func(context.Context, string) (*formsapi.Service, error) { return svc, nil }
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "forms", "responses", "list", "form123", "--max", "1"}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
|
|
var parsed struct {
|
|
FormID string `json:"form_id"`
|
|
Responses []struct {
|
|
ResponseID string `json:"responseId"`
|
|
} `json:"responses"`
|
|
NextPageToken string `json:"nextPageToken"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &parsed); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if parsed.FormID != "form123" || len(parsed.Responses) != 1 || parsed.Responses[0].ResponseID != "r1" || parsed.NextPageToken != "next123" {
|
|
t.Fatalf("unexpected payload: %#v", parsed)
|
|
}
|
|
}
|
|
|
|
func TestExecute_FormsResponsesList_RejectsNonPositiveMax(t *testing.T) {
|
|
origNew := newFormsService
|
|
t.Cleanup(func() { newFormsService = origNew })
|
|
newFormsService = func(context.Context, string) (*formsapi.Service, error) {
|
|
t.Fatalf("expected validation to fail before creating forms service")
|
|
return nil, errors.New("unexpected forms service call")
|
|
}
|
|
|
|
_ = captureStderr(t, func() {
|
|
err := Execute([]string{"--account", "a@b.com", "forms", "responses", "list", "form123", "--max", "0"})
|
|
if err == nil || !strings.Contains(err.Error(), "--max must be > 0") {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestExecute_FormsCreate_DryRun_JSON(t *testing.T) {
|
|
origNew := newFormsService
|
|
t.Cleanup(func() { newFormsService = origNew })
|
|
newFormsService = func(context.Context, string) (*formsapi.Service, error) {
|
|
t.Fatalf("dry-run should not create forms service")
|
|
return nil, errors.New("unexpected forms service call")
|
|
}
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{
|
|
"--json",
|
|
"--dry-run",
|
|
"--account", "a@b.com",
|
|
"forms", "create",
|
|
"--title", "Weekly Check-in",
|
|
"--description", "Friday async update",
|
|
}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
|
|
var parsed struct {
|
|
DryRun bool `json:"dry_run"`
|
|
Op string `json:"op"`
|
|
Request struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
} `json:"request"`
|
|
}
|
|
if err := json.Unmarshal([]byte(out), &parsed); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
if !parsed.DryRun || parsed.Op != "forms.create" {
|
|
t.Fatalf("unexpected dry-run payload: %#v", parsed)
|
|
}
|
|
if parsed.Request.Title != "Weekly Check-in" || parsed.Request.Description != "Friday async update" {
|
|
t.Fatalf("unexpected request payload: %#v", parsed.Request)
|
|
}
|
|
}
|
|
|
|
func TestExecute_AppScriptRun_JSON(t *testing.T) {
|
|
origNew := newAppScriptService
|
|
t.Cleanup(func() { newAppScriptService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !(strings.Contains(r.URL.Path, "/scripts/script123:run") && r.Method == http.MethodPost) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
var req map[string]any
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
t.Fatalf("decode request: %v", err)
|
|
}
|
|
if req["function"] != "myFunc" {
|
|
t.Fatalf("unexpected function: %#v", req["function"])
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"done": true,
|
|
"response": map[string]any{
|
|
"result": map[string]any{
|
|
"ok": true,
|
|
},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
svc, err := scriptapi.NewService(context.Background(),
|
|
option.WithoutAuthentication(),
|
|
option.WithHTTPClient(srv.Client()),
|
|
option.WithEndpoint(srv.URL+"/"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
newAppScriptService = func(context.Context, string) (*scriptapi.Service, error) { return svc, nil }
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--json", "--account", "a@b.com", "appscript", "run", "script123", "myFunc", "--params", "[\"x\"]"}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
|
|
var parsed map[string]any
|
|
if err := json.Unmarshal([]byte(out), &parsed); err != nil {
|
|
t.Fatalf("unmarshal: %v", err)
|
|
}
|
|
op, ok := parsed["operation"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("missing operation: %#v", parsed)
|
|
}
|
|
if done, _ := op["done"].(bool); !done {
|
|
t.Fatalf("expected done=true: %#v", op)
|
|
}
|
|
}
|
|
|
|
func TestExecute_AppScriptCreate_DryRun_Text(t *testing.T) {
|
|
origNew := newAppScriptService
|
|
t.Cleanup(func() { newAppScriptService = origNew })
|
|
newAppScriptService = func(context.Context, string) (*scriptapi.Service, error) {
|
|
t.Fatalf("dry-run should not create appscript service")
|
|
return nil, errors.New("unexpected appscript service call")
|
|
}
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{
|
|
"--dry-run",
|
|
"--account", "a@b.com",
|
|
"appscript", "create",
|
|
"--title", "Automation Helpers",
|
|
"--parent-id", "drive123",
|
|
}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(out, "Dry run: would appscript.create") ||
|
|
!strings.Contains(out, `"title": "Automation Helpers"`) ||
|
|
!strings.Contains(out, `"parent_id": "drive123"`) {
|
|
t.Fatalf("unexpected dry-run out=%q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecute_AppScriptRun_RejectsNonArrayParams(t *testing.T) {
|
|
origNew := newAppScriptService
|
|
t.Cleanup(func() { newAppScriptService = origNew })
|
|
newAppScriptService = func(context.Context, string) (*scriptapi.Service, error) {
|
|
t.Fatalf("expected params validation to fail before creating appscript service")
|
|
return nil, errors.New("unexpected appscript service call")
|
|
}
|
|
|
|
_ = captureStderr(t, func() {
|
|
err := Execute([]string{"--account", "a@b.com", "appscript", "run", "script123", "myFunc", "--params", `{"x":1}`})
|
|
if err == nil || !strings.Contains(err.Error(), "invalid --params JSON array") {
|
|
t.Fatalf("unexpected err: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestExecute_AppScriptRun_TextErrorDetails(t *testing.T) {
|
|
origNew := newAppScriptService
|
|
t.Cleanup(func() { newAppScriptService = origNew })
|
|
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if !(strings.Contains(r.URL.Path, "/scripts/script123:run") && r.Method == http.MethodPost) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]any{
|
|
"done": true,
|
|
"error": map[string]any{
|
|
"code": 3,
|
|
"message": "Script execution failed",
|
|
"details": []map[string]any{
|
|
{
|
|
"@type": "type.googleapis.com/google.apps.script.type.ExecutionError",
|
|
"errorType": "TypeError",
|
|
"errorMessage": "boom",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
svc, err := scriptapi.NewService(context.Background(),
|
|
option.WithoutAuthentication(),
|
|
option.WithHTTPClient(srv.Client()),
|
|
option.WithEndpoint(srv.URL+"/"),
|
|
)
|
|
if err != nil {
|
|
t.Fatalf("NewService: %v", err)
|
|
}
|
|
newAppScriptService = func(context.Context, string) (*scriptapi.Service, error) { return svc, nil }
|
|
|
|
out := captureStdout(t, func() {
|
|
_ = captureStderr(t, func() {
|
|
if err := Execute([]string{"--account", "a@b.com", "appscript", "run", "script123", "myFunc"}); err != nil {
|
|
t.Fatalf("Execute: %v", err)
|
|
}
|
|
})
|
|
})
|
|
if !strings.Contains(out, "done\ttrue") ||
|
|
!strings.Contains(out, "error_code\t3") ||
|
|
!strings.Contains(out, "error\tScript execution failed") ||
|
|
!strings.Contains(out, "error_type\tTypeError") ||
|
|
!strings.Contains(out, "error_message\tboom") {
|
|
t.Fatalf("unexpected out=%q", out)
|
|
}
|
|
}
|