gogcli/internal/cmd/docs_commands_test.go
Peter Steinberger 922ca38b3b
fix(docs): show available tabs on lookup errors
Co-authored-by: Ben Lewis <johnbenjaminlewis@gmail.com>
2026-04-27 10:12:24 +01:00

893 lines
26 KiB
Go

package cmd
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"google.golang.org/api/docs/v1"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
"github.com/steipete/gogcli/internal/outfmt"
"github.com/steipete/gogcli/internal/ui"
)
func TestDocsCreateCopyCat_JSON(t *testing.T) {
origNew := newDriveService
origDocs := newDocsService
origExport := driveExportDownload
t.Cleanup(func() {
newDriveService = origNew
newDocsService = origDocs
driveExportDownload = origExport
})
driveExportDownload = func(context.Context, *drive.Service, string, string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("doc text")),
}, nil
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
drivePath := strings.TrimPrefix(path, "/drive/v3")
switch {
case strings.HasPrefix(path, "/v1/documents/") && r.Method == http.MethodGet:
id := strings.TrimPrefix(path, "/v1/documents/")
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"documentId": id,
"title": "Doc",
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{
"content": "doc text",
},
},
},
},
},
},
},
})
return
case strings.HasPrefix(drivePath, "/files/") && r.Method == http.MethodGet:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc1",
"mimeType": "application/vnd.google-apps.document",
})
return
case drivePath == "/files" && r.Method == http.MethodPost:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc1",
"name": "Doc",
"mimeType": "application/vnd.google-apps.document",
"webViewLink": "http://example.com/doc1",
})
return
case strings.Contains(drivePath, "/files/doc1/copy") && r.Method == http.MethodPost:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc2",
"name": "Copy",
"mimeType": "application/vnd.google-apps.document",
"webViewLink": "http://example.com/doc2",
})
return
default:
http.NotFound(w, r)
return
}
}))
defer srv.Close()
svc, err := drive.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewService: %v", err)
}
newDriveService = func(context.Context, string) (*drive.Service, error) { return svc, nil }
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
if uiErr != nil {
t.Fatalf("ui.New: %v", uiErr)
}
ctx := ui.WithUI(context.Background(), u)
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
_ = captureStdout(t, func() {
cmd := &DocsCreateCmd{}
if err := runKong(t, cmd, []string{"Doc"}, ctx, flags); err != nil {
t.Fatalf("create: %v", err)
}
})
_ = captureStdout(t, func() {
cmd := &DocsCopyCmd{}
if err := runKong(t, cmd, []string{"doc1", "Copy"}, ctx, flags); err != nil {
t.Fatalf("copy: %v", err)
}
})
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1"}, ctx, flags); err != nil {
t.Fatalf("cat: %v", err)
}
})
if !strings.Contains(out, "doc text") {
t.Fatalf("unexpected cat output: %q", out)
}
}
func TestDocsCreate_Pageless(t *testing.T) {
origNew := newDriveService
origDocs := newDocsService
t.Cleanup(func() {
newDriveService = origNew
newDocsService = origDocs
})
var batchRequests [][]*docs.Request
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
drivePath := strings.TrimPrefix(path, "/drive/v3")
switch {
case drivePath == "/files" && r.Method == http.MethodPost:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc1",
"name": "Doc",
"mimeType": "application/vnd.google-apps.document",
"webViewLink": "http://example.com/doc1",
})
return
case r.Method == http.MethodPost && strings.Contains(path, ":batchUpdate"):
var req docs.BatchUpdateDocumentRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
t.Fatalf("decode request: %v", err)
}
batchRequests = append(batchRequests, req.Requests)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{"documentId": "doc1"})
return
default:
http.NotFound(w, r)
return
}
}))
defer srv.Close()
driveSvc, err := drive.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewService: %v", err)
}
newDriveService = func(context.Context, string) (*drive.Service, error) { return driveSvc, nil }
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, uiErr := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
if uiErr != nil {
t.Fatalf("ui.New: %v", uiErr)
}
ctx := outfmt.WithMode(ui.WithUI(context.Background(), u), outfmt.Mode{JSON: true})
_ = captureStdout(t, func() {
cmd := &DocsCreateCmd{}
if err := runKong(t, cmd, []string{"Doc", "--pageless"}, ctx, flags); err != nil {
t.Fatalf("create pageless: %v", err)
}
})
if len(batchRequests) != 1 {
t.Fatalf("expected 1 pageless batch request, got %d", len(batchRequests))
}
if got := batchRequests[0]; len(got) != 1 || got[0].UpdateDocumentStyle == nil {
t.Fatalf("unexpected pageless create request: %#v", got)
}
if got := batchRequests[0][0].UpdateDocumentStyle; got.Fields != "documentFormat" || got.DocumentStyle.DocumentFormat.DocumentMode != "PAGELESS" {
t.Fatalf("unexpected pageless create style request: %#v", got)
}
}
func TestDocsCreate_DryRunDoesNotOpenService(t *testing.T) {
origNew := newDriveService
t.Cleanup(func() { newDriveService = origNew })
newDriveService = func(context.Context, string) (*drive.Service, error) {
t.Fatal("newDriveService should not be called during dry-run")
return nil, errors.New("unexpected drive service call")
}
out := captureStdout(t, func() {
err := (&DocsCreateCmd{
Title: "Dry Run",
Parent: "folder1",
Pageless: true,
}).Run(newDocsJSONContext(t), &RootFlags{Account: "a@b.com", DryRun: true})
var exitErr *ExitError
if !errors.As(err, &exitErr) || exitErr.Code != 0 {
t.Fatalf("expected dry-run exit 0, got %v", err)
}
})
var payload struct {
DryRun bool `json:"dry_run"`
Op string `json:"op"`
Request struct {
File drive.File `json:"file"`
Parent string `json:"parent"`
Pageless bool `json:"pageless"`
} `json:"request"`
}
if err := json.Unmarshal([]byte(out), &payload); err != nil {
t.Fatalf("decode dry-run: %v\nout=%q", err, out)
}
if !payload.DryRun || payload.Op != "docs.create" || payload.Request.File.Name != "Dry Run" || payload.Request.Parent != "folder1" || !payload.Request.Pageless {
t.Fatalf("unexpected dry-run output: %#v", payload)
}
}
// tabsDocResponse returns a JSON response for a document with multiple tabs
// (using includeTabsContent=true). The body/content fields are empty because
// the Docs API populates doc.Tabs instead when that flag is set.
func tabsDocResponse(id string) map[string]any {
return map[string]any{
"documentId": id,
"title": "Multi-Tab Doc",
"tabs": []any{
map[string]any{
"tabProperties": map[string]any{
"tabId": "t.0",
"title": "Overview",
"index": 0,
},
"documentTab": map[string]any{
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{"content": "overview text"},
},
},
},
},
},
},
},
},
map[string]any{
"tabProperties": map[string]any{
"tabId": "t.abc",
"title": "Details",
"index": 1,
},
"documentTab": map[string]any{
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{"content": "details text"},
},
},
},
},
},
},
},
"childTabs": []any{
map[string]any{
"tabProperties": map[string]any{
"tabId": "t.child1",
"title": "Sub-Detail",
"index": 0,
"nestingLevel": 1,
"parentTabId": "t.abc",
},
"documentTab": map[string]any{
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{"content": "child text"},
},
},
},
},
},
},
},
},
},
},
},
}
}
func newTabsTestServer(t *testing.T) (*docs.Service, func()) {
t.Helper()
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
if strings.HasPrefix(path, "/v1/documents/") && r.Method == http.MethodGet {
id := strings.TrimPrefix(path, "/v1/documents/")
w.Header().Set("Content-Type", "application/json")
// Check if includeTabsContent is requested.
if r.URL.Query().Get("includeTabsContent") == "true" {
_ = json.NewEncoder(w).Encode(tabsDocResponse(id))
} else {
_ = json.NewEncoder(w).Encode(map[string]any{
"documentId": id,
"title": "Multi-Tab Doc",
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{"content": "overview text"},
},
},
},
},
},
},
})
}
return
}
http.NotFound(w, r)
}))
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
return docSvc, srv.Close
}
func TestDocsCat_DefaultNoTabs(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1"}, ctx, flags); err != nil {
t.Fatalf("cat: %v", err)
}
})
if !strings.Contains(out, "overview text") {
t.Fatalf("expected default tab text, got: %q", out)
}
if strings.Contains(out, "=== Tab:") {
t.Fatal("default mode should not show tab headers")
}
}
func TestDocsCat_AllTabs(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--all-tabs"}, ctx, flags); err != nil {
t.Fatalf("cat --all-tabs: %v", err)
}
})
if !strings.Contains(out, "=== Tab: Overview ===") {
t.Fatalf("missing Overview tab header in: %q", out)
}
if !strings.Contains(out, "=== Tab: Details ===") {
t.Fatalf("missing Details tab header in: %q", out)
}
if !strings.Contains(out, "=== Tab: Sub-Detail ===") {
t.Fatalf("missing Sub-Detail (child) tab header in: %q", out)
}
if !strings.Contains(out, "overview text") || !strings.Contains(out, "details text") || !strings.Contains(out, "child text") {
t.Fatalf("missing tab content in: %q", out)
}
}
func TestDocsCat_AllTabs_JSON(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--all-tabs"}, ctx, flags); err != nil {
t.Fatalf("cat --all-tabs --json: %v", err)
}
})
var result map[string]any
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("JSON parse: %v\nraw: %q", err, out)
}
tabs, ok := result["tabs"].([]any)
if !ok || len(tabs) != 3 {
t.Fatalf("expected 3 tabs in JSON, got: %v", result)
}
first := tabs[0].(map[string]any)
if first["title"] != "Overview" || first["id"] != "t.0" {
t.Fatalf("unexpected first tab: %v", first)
}
}
func TestDocsCat_Raw(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--raw"}, ctx, flags); err != nil {
t.Fatalf("cat --raw: %v", err)
}
})
var result map[string]any
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("raw JSON parse: %v\nraw: %q", err, out)
}
// Raw output should contain the documentId field from the API response.
if result["documentId"] != "doc1" {
t.Fatalf("expected documentId=doc1, got: %v", result["documentId"])
}
// Should be pretty-printed (contain newlines + indentation).
if !strings.Contains(out, "\n ") {
t.Fatal("expected pretty-printed JSON with indentation")
}
}
func TestDocsCat_Raw_AllTabs(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--raw", "--all-tabs"}, ctx, flags); err != nil {
t.Fatalf("cat --raw --all-tabs: %v", err)
}
})
var result map[string]any
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("raw JSON parse: %v\nraw: %q", err, out)
}
// With --all-tabs, the raw response should include tabs content.
if _, ok := result["tabs"]; !ok {
t.Fatal("expected tabs field in raw --all-tabs output")
}
}
func TestDocsCat_SingleTab(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
// By title.
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--tab", "Details"}, ctx, flags); err != nil {
t.Fatalf("cat --tab Details: %v", err)
}
})
if !strings.Contains(out, "details text") {
t.Fatalf("expected details text, got: %q", out)
}
if strings.Contains(out, "overview text") {
t.Fatal("should not contain other tab text")
}
// By ID.
out = captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--tab", "t.child1"}, ctx, flags); err != nil {
t.Fatalf("cat --tab t.child1: %v", err)
}
})
if !strings.Contains(out, "child text") {
t.Fatalf("expected child text, got: %q", out)
}
}
func TestDocsCat_TabNotFound(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
cmd := &DocsCatCmd{}
err := runKong(t, cmd, []string{"doc1", "--tab", "Nonexistent"}, ctx, flags)
if err == nil || !strings.Contains(err.Error(), "tab not found") {
t.Fatalf("expected tab not found error, got: %v", err)
}
if !strings.Contains(err.Error(), "Overview") || !strings.Contains(err.Error(), "Details") {
t.Fatalf("expected available tab names in error, got: %v", err)
}
}
func TestDocsCat_SingleTab_JSON(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
ctx = outfmt.WithMode(ctx, outfmt.Mode{JSON: true})
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--tab", "Overview"}, ctx, flags); err != nil {
t.Fatalf("cat --tab Overview --json: %v", err)
}
})
var result map[string]any
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("JSON parse: %v\nraw: %q", err, out)
}
tab, ok := result["tab"].(map[string]any)
if !ok {
t.Fatalf("expected tab object, got: %v", result)
}
if tab["title"] != "Overview" || tab["text"] != "overview text" {
t.Fatalf("unexpected tab: %v", tab)
}
}
func TestDocsCat_CaseInsensitiveTabTitle(t *testing.T) {
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
docSvc, cleanup := newTabsTestServer(t)
defer cleanup()
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
out := captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1", "--tab", "details"}, ctx, flags); err != nil {
t.Fatalf("cat --tab details (lowercase): %v", err)
}
})
if !strings.Contains(out, "details text") {
t.Fatalf("case-insensitive match failed, got: %q", out)
}
}
func TestDocsCat_BackwardCompatibility(t *testing.T) {
// Verify that docs cat without --tab or --all-tabs does NOT send
// includeTabsContent parameter (backward compatible).
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
var gotIncludeTabs bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("includeTabsContent") == "true" {
gotIncludeTabs = true
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"documentId": "doc1",
"title": "Doc",
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{"content": "hello"},
},
},
},
},
},
},
})
}))
defer srv.Close()
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
_ = captureStdout(t, func() {
cmd := &DocsCatCmd{}
if err := runKong(t, cmd, []string{"doc1"}, ctx, flags); err != nil {
t.Fatalf("cat: %v", err)
}
})
if gotIncludeTabs {
t.Fatal("default cat should NOT send includeTabsContent=true")
}
}
func TestDocsCat_TabSendsIncludeTabsContent(t *testing.T) {
// Verify that --tab sends includeTabsContent=true.
origDocs := newDocsService
t.Cleanup(func() { newDocsService = origDocs })
var gotIncludeTabs bool
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("includeTabsContent") == "true" {
gotIncludeTabs = true
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(tabsDocResponse("doc1"))
}))
defer srv.Close()
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
u, _ := ui.New(ui.Options{Stdout: io.Discard, Stderr: io.Discard, Color: "never"})
ctx := ui.WithUI(context.Background(), u)
_ = captureStdout(t, func() {
cmd := &DocsCatCmd{}
_ = runKong(t, cmd, []string{"doc1", "--tab", "Overview"}, ctx, flags)
})
if !gotIncludeTabs {
t.Fatal("--tab should send includeTabsContent=true")
}
}
func TestDocsCreateCopyCat_Text(t *testing.T) {
origNew := newDriveService
origDocs := newDocsService
origExport := driveExportDownload
t.Cleanup(func() {
newDriveService = origNew
newDocsService = origDocs
driveExportDownload = origExport
})
driveExportDownload = func(context.Context, *drive.Service, string, string) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader("doc text")),
}, nil
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
drivePath := strings.TrimPrefix(path, "/drive/v3")
switch {
case strings.HasPrefix(path, "/v1/documents/") && r.Method == http.MethodGet:
id := strings.TrimPrefix(path, "/v1/documents/")
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"documentId": id,
"title": "Doc",
"body": map[string]any{
"content": []any{
map[string]any{
"paragraph": map[string]any{
"elements": []any{
map[string]any{
"textRun": map[string]any{
"content": "doc text",
},
},
},
},
},
},
},
})
return
case strings.HasPrefix(drivePath, "/files/") && r.Method == http.MethodGet:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc1",
"mimeType": "application/vnd.google-apps.document",
})
return
case drivePath == "/files" && r.Method == http.MethodPost:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc1",
"name": "Doc",
"mimeType": "application/vnd.google-apps.document",
"webViewLink": "http://example.com/doc1",
})
return
case strings.Contains(drivePath, "/files/doc1/copy") && r.Method == http.MethodPost:
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"id": "doc2",
"name": "Copy",
"mimeType": "application/vnd.google-apps.document",
"webViewLink": "http://example.com/doc2",
})
return
default:
http.NotFound(w, r)
return
}
}))
defer srv.Close()
svc, err := drive.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewService: %v", err)
}
newDriveService = func(context.Context, string) (*drive.Service, error) { return svc, nil }
docSvc, err := docs.NewService(context.Background(),
option.WithoutAuthentication(),
option.WithHTTPClient(srv.Client()),
option.WithEndpoint(srv.URL+"/"),
)
if err != nil {
t.Fatalf("NewDocsService: %v", err)
}
newDocsService = func(context.Context, string) (*docs.Service, error) { return docSvc, nil }
flags := &RootFlags{Account: "a@b.com"}
out := captureStdout(t, func() {
u, uiErr := ui.New(ui.Options{Stdout: os.Stdout, Stderr: io.Discard, Color: "never"})
if uiErr != nil {
t.Fatalf("ui.New: %v", uiErr)
}
ctx := ui.WithUI(context.Background(), u)
createCmd := &DocsCreateCmd{}
if err := runKong(t, createCmd, []string{"Doc"}, ctx, flags); err != nil {
t.Fatalf("create: %v", err)
}
copyCmd := &DocsCopyCmd{}
if err := runKong(t, copyCmd, []string{"doc1", "Copy"}, ctx, flags); err != nil {
t.Fatalf("copy: %v", err)
}
catCmd := &DocsCatCmd{}
if err := runKong(t, catCmd, []string{"doc1"}, ctx, flags); err != nil {
t.Fatalf("cat: %v", err)
}
})
if !strings.Contains(out, "doc text") || !strings.Contains(out, "id\tdoc1") {
t.Fatalf("unexpected output: %q", out)
}
}