From 9aeb7d7b16f43df798d4ecc9a830fe4f900b0570 Mon Sep 17 00:00:00 2001 From: Keir Lavelle Date: Sun, 1 Feb 2026 20:39:03 +0000 Subject: [PATCH 1/5] feat(DCP-2356): Add AITB Collection Preview command --- cmd/collection/collection.go | 1 + cmd/collection/preview.go | 73 ++++++++++++ cmd/collection/preview_test.go | 206 +++++++++++++++++++++++++++++++++ config/config.go | 16 ++- ui/collection/view.go | 11 ++ ui/collection/view_test.go | 61 ++++++++++ 6 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 cmd/collection/preview.go create mode 100644 cmd/collection/preview_test.go create mode 100644 ui/collection/view_test.go diff --git a/cmd/collection/collection.go b/cmd/collection/collection.go index 36c3be8..8170c22 100644 --- a/cmd/collection/collection.go +++ b/cmd/collection/collection.go @@ -20,6 +20,7 @@ func NewCollectionCommand(client client.API, w io.Writer) *cobra.Command { NewCreateCollectionCommand(client, w), NewUpdateCommand(client, w), NewPublishCommand(client, w), + NewPreviewCommand(client, w), ) return cmd } diff --git a/cmd/collection/preview.go b/cmd/collection/preview.go new file mode 100644 index 0000000..65740a8 --- /dev/null +++ b/cmd/collection/preview.go @@ -0,0 +1,73 @@ +package collection + +import ( + "errors" + "fmt" + "io" + + "github.com/pkg/browser" + "github.com/prolific-oss/cli/client" + "github.com/prolific-oss/cli/cmd/shared" + "github.com/prolific-oss/cli/ui" + collectionui "github.com/prolific-oss/cli/ui/collection" + "github.com/spf13/cobra" +) + +// PreviewOptions is the options for the preview collection command. +type PreviewOptions struct { + Args []string +} + +// NewPreviewCommand creates a new `collection preview` command to open a collection +// preview in the browser. +func NewPreviewCommand(c client.API, w io.Writer) *cobra.Command { + var opts PreviewOptions + + cmd := &cobra.Command{ + Use: "preview ", + Args: cobra.MinimumNArgs(1), + Short: "Preview a collection in the browser", + Long: `Preview a collection in the browser + +Opens the collection in your default web browser so you can preview +it before publishing.`, + Example: ` +Preview a collection in the browser + +$ prolific collection preview 123456789 +`, + RunE: func(cmd *cobra.Command, args []string) error { + opts.Args = args + + if len(opts.Args) < 1 || opts.Args[0] == "" { + return errors.New("please provide a collection ID") + } + + collectionID := opts.Args[0] + + // Fetch collection to validate access + _, err := c.GetCollection(collectionID) + if err != nil { + if shared.IsFeatureNotEnabledError(err) { + ui.RenderFeatureAccessMessage(FeatureNameAITBCollection, FeatureContactURLAITBCollection) + return nil + } + return fmt.Errorf("error: %s", err.Error()) + } + + // Open the collection preview in the browser + previewURL := collectionui.GetCollectionPreviewURL(collectionID) + if err := browser.OpenURL(previewURL); err != nil { + return fmt.Errorf("failed to open browser: %s", err.Error()) + } + + fmt.Fprintln(w, "Opening collection preview in browser...") + fmt.Fprintln(w) + fmt.Fprintln(w, previewURL) + + return nil + }, + } + + return cmd +} diff --git a/cmd/collection/preview_test.go b/cmd/collection/preview_test.go new file mode 100644 index 0000000..73b07b6 --- /dev/null +++ b/cmd/collection/preview_test.go @@ -0,0 +1,206 @@ +package collection_test + +import ( + "bufio" + "bytes" + "errors" + "strings" + "testing" + "time" + + "github.com/golang/mock/gomock" + "github.com/prolific-oss/cli/cmd/collection" + "github.com/prolific-oss/cli/mock_client" + "github.com/prolific-oss/cli/model" +) + +func TestNewPreviewCommand(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + var buf bytes.Buffer + cmd := collection.NewPreviewCommand(mockClient, &buf) + + use := "preview " + short := "Preview a collection in the browser" + + if cmd.Use != use { + t.Fatalf("expected use: %s; got %s", use, cmd.Use) + } + + if cmd.Short != short { + t.Fatalf("expected short: %s; got %s", short, cmd.Short) + } +} + +func TestPreviewCommandRequiresCollectionID(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + var buf bytes.Buffer + cmd := collection.NewPreviewCommand(mockClient, &buf) + + err := cmd.RunE(cmd, []string{}) + if err == nil { + t.Fatalf("expected error for missing collection ID, got nil") + } + + expected := "please provide a collection ID" + if err.Error() != expected { + t.Fatalf("expected error %q, got %q", expected, err.Error()) + } +} + +func TestPreviewCommandRequiresNonEmptyCollectionID(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + var buf bytes.Buffer + cmd := collection.NewPreviewCommand(mockClient, &buf) + + err := cmd.RunE(cmd, []string{""}) + if err == nil { + t.Fatalf("expected error for empty collection ID, got nil") + } + + expected := "please provide a collection ID" + if err.Error() != expected { + t.Fatalf("expected error %q, got %q", expected, err.Error()) + } +} + +func TestPreviewCommandCallsGetCollection(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + testCollection := &model.Collection{ + ID: testCollectionID, + Name: "Test Collection", + CreatedAt: time.Now(), + CreatedBy: "test-user", + ItemCount: 10, + } + + mockClient. + EXPECT(). + GetCollection(gomock.Eq(testCollectionID)). + Return(testCollection, nil). + Times(1) + + var buf bytes.Buffer + writer := bufio.NewWriter(&buf) + cmd := collection.NewPreviewCommand(mockClient, writer) + + // Note: This will attempt to open the browser, but we're just testing that + // the GetCollection call is made and no error is returned + err := cmd.RunE(cmd, []string{testCollectionID}) + writer.Flush() + + // We don't fail on browser.OpenURL errors in CI environments + if err != nil && !strings.Contains(err.Error(), "browser") { + t.Fatalf("expected no error (or browser error), got: %v", err) + } +} + +func TestPreviewCommandReturnsErrorOnClientError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + mockClient. + EXPECT(). + GetCollection(gomock.Eq(testCollectionID)). + Return(nil, errors.New("collection not found")). + Times(1) + + var buf bytes.Buffer + writer := bufio.NewWriter(&buf) + cmd := collection.NewPreviewCommand(mockClient, writer) + + err := cmd.RunE(cmd, []string{testCollectionID}) + writer.Flush() + + if err == nil { + t.Fatal("expected error, got nil") + } + + expected := "error: collection not found" + if err.Error() != expected { + t.Fatalf("expected error %q, got %q", expected, err.Error()) + } +} + +func TestPreviewCommandHandlesFeatureNotEnabledError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + // The feature not enabled error must contain "request failed", "permission", and "feature" + featureError := errors.New("request failed: you do not currently have permission to access this feature") + + mockClient. + EXPECT(). + GetCollection(gomock.Eq(testCollectionID)). + Return(nil, featureError). + Times(1) + + var buf bytes.Buffer + writer := bufio.NewWriter(&buf) + cmd := collection.NewPreviewCommand(mockClient, writer) + + err := cmd.RunE(cmd, []string{testCollectionID}) + writer.Flush() + + // When feature is not enabled, the command should not return an error + // but should display a feature access message + if err != nil { + t.Fatalf("expected no error for feature not enabled, got: %v", err) + } +} + +func TestPreviewCommandOutputContainsURL(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockClient := mock_client.NewMockAPI(ctrl) + + testCollection := &model.Collection{ + ID: testCollectionID, + Name: "My Test Collection", + CreatedAt: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC), + CreatedBy: "user@example.com", + ItemCount: 25, + } + + mockClient. + EXPECT(). + GetCollection(gomock.Eq(testCollectionID)). + Return(testCollection, nil). + Times(1) + + var buf bytes.Buffer + writer := bufio.NewWriter(&buf) + cmd := collection.NewPreviewCommand(mockClient, writer) + + // Run the command - may fail to open browser in CI + _ = cmd.RunE(cmd, []string{testCollectionID}) + writer.Flush() + + output := buf.String() + + // Check that the output contains the expected URL components + expectedStrings := []string{ + "Opening collection preview in browser", + "data-collection-tool/collections/" + testCollectionID, + "preview=true", + } + + for _, expected := range expectedStrings { + if !strings.Contains(output, expected) { + t.Errorf("expected output to contain %q, got: %s", expected, output) + } + } +} diff --git a/config/config.go b/config/config.go index 1423db7..da2e15f 100644 --- a/config/config.go +++ b/config/config.go @@ -2,10 +2,20 @@ // including base URLs for the Prolific application and API. package config -// GetApplicationURL will return the Application URL. This could be updated -// to understand different environments based on API URL perhaps? +import ( + "strings" + + "github.com/spf13/viper" +) + +// DefaultApplicationURL is the default Prolific application URL. +const DefaultApplicationURL = "https://app.prolific.com" + +// GetApplicationURL will return the Application URL. This can be overridden +// using the PROLIFIC_APPLICATION_URL environment variable. func GetApplicationURL() string { - return "https://app.prolific.com" + viper.SetDefault("PROLIFIC_APPLICATION_URL", DefaultApplicationURL) + return strings.TrimRight(viper.GetString("PROLIFIC_APPLICATION_URL"), "/") } // GetAPIURL will return the API URL. This is the default API, but we allow diff --git a/ui/collection/view.go b/ui/collection/view.go index 37143ab..bf26706 100644 --- a/ui/collection/view.go +++ b/ui/collection/view.go @@ -8,6 +8,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/prolific-oss/cli/client" + "github.com/prolific-oss/cli/config" "github.com/prolific-oss/cli/model" "github.com/prolific-oss/cli/ui" ) @@ -93,3 +94,13 @@ func renderCollectionString(collection model.Collection) string { return content } + +// GetCollectionPreviewPath returns the URL path to a collection preview, agnostic of domain +func GetCollectionPreviewPath(ID string) string { + return fmt.Sprintf("data-collection-tool/collections/%s?preview=true", ID) +} + +// GetCollectionPreviewURL returns the full URL to a collection preview using configuration +func GetCollectionPreviewURL(ID string) string { + return fmt.Sprintf("%s/%s", config.GetApplicationURL(), GetCollectionPreviewPath(ID)) +} diff --git a/ui/collection/view_test.go b/ui/collection/view_test.go new file mode 100644 index 0000000..196d3e1 --- /dev/null +++ b/ui/collection/view_test.go @@ -0,0 +1,61 @@ +package collection_test + +import ( + "strings" + "testing" + + "github.com/prolific-oss/cli/config" + "github.com/prolific-oss/cli/ui/collection" +) + +func TestGetCollectionPreviewPath(t *testing.T) { + collectionID := "test-collection-123" + + path := collection.GetCollectionPreviewPath(collectionID) + + expectedPath := "data-collection-tool/collections/test-collection-123?preview=true" + if path != expectedPath { + t.Fatalf("expected path %q, got %q", expectedPath, path) + } +} + +func TestGetCollectionPreviewPathContainsPreviewParam(t *testing.T) { + collectionID := "abc123" + + path := collection.GetCollectionPreviewPath(collectionID) + + if !strings.Contains(path, "preview=true") { + t.Fatalf("expected path to contain 'preview=true', got %q", path) + } +} + +func TestGetCollectionPreviewURL(t *testing.T) { + collectionID := "test-collection-456" + + url := collection.GetCollectionPreviewURL(collectionID) + + expectedURL := config.GetApplicationURL() + "/data-collection-tool/collections/test-collection-456?preview=true" + if url != expectedURL { + t.Fatalf("expected URL %q, got %q", expectedURL, url) + } +} + +func TestGetCollectionPreviewURLUsesApplicationURL(t *testing.T) { + collectionID := "xyz789" + + url := collection.GetCollectionPreviewURL(collectionID) + + if !strings.HasPrefix(url, config.GetApplicationURL()) { + t.Fatalf("expected URL to start with application URL %q, got %q", config.GetApplicationURL(), url) + } +} + +func TestGetCollectionPreviewURLContainsCollectionID(t *testing.T) { + collectionID := "my-unique-id" + + url := collection.GetCollectionPreviewURL(collectionID) + + if !strings.Contains(url, collectionID) { + t.Fatalf("expected URL to contain collection ID %q, got %q", collectionID, url) + } +} From c2479f0fd7b68e7227de50b7ee174fddbf0a8ef0 Mon Sep 17 00:00:00 2001 From: Keir Lavelle Date: Sun, 1 Feb 2026 20:54:12 +0000 Subject: [PATCH 2/5] fix(DCP-2356): Stop browser opening behaviour in tests --- cmd/collection/preview.go | 29 +++++++++++++++++++++++------ cmd/collection/preview_test.go | 33 +++++++++++++++++++-------------- 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/cmd/collection/preview.go b/cmd/collection/preview.go index 65740a8..d8db72a 100644 --- a/cmd/collection/preview.go +++ b/cmd/collection/preview.go @@ -13,15 +13,30 @@ import ( "github.com/spf13/cobra" ) +// BrowserOpener is a function type for opening URLs in a browser. +// This allows for dependency injection in tests. +type BrowserOpener func(url string) error + +// DefaultBrowserOpener uses the system browser to open URLs. +var DefaultBrowserOpener BrowserOpener = browser.OpenURL + // PreviewOptions is the options for the preview collection command. type PreviewOptions struct { - Args []string + Args []string + BrowserOpener BrowserOpener } // NewPreviewCommand creates a new `collection preview` command to open a collection // preview in the browser. func NewPreviewCommand(c client.API, w io.Writer) *cobra.Command { + return NewPreviewCommandWithOpener(c, w, DefaultBrowserOpener) +} + +// NewPreviewCommandWithOpener creates a new `collection preview` command with a custom browser opener. +// This is useful for testing to avoid opening actual browser windows. +func NewPreviewCommandWithOpener(c client.API, w io.Writer, browserOpener BrowserOpener) *cobra.Command { var opts PreviewOptions + opts.BrowserOpener = browserOpener cmd := &cobra.Command{ Use: "preview ", @@ -55,16 +70,18 @@ $ prolific collection preview 123456789 return fmt.Errorf("error: %s", err.Error()) } - // Open the collection preview in the browser + // Build the preview URL and display it previewURL := collectionui.GetCollectionPreviewURL(collectionID) - if err := browser.OpenURL(previewURL); err != nil { - return fmt.Errorf("failed to open browser: %s", err.Error()) - } - fmt.Fprintln(w, "Opening collection preview in browser...") fmt.Fprintln(w) fmt.Fprintln(w, previewURL) + // Attempt to open the browser - don't fail if it doesn't work + // (e.g., in headless/CI environments) + if opts.BrowserOpener != nil { + _ = opts.BrowserOpener(previewURL) + } + return nil }, } diff --git a/cmd/collection/preview_test.go b/cmd/collection/preview_test.go index 73b07b6..c047a3c 100644 --- a/cmd/collection/preview_test.go +++ b/cmd/collection/preview_test.go @@ -14,13 +14,18 @@ import ( "github.com/prolific-oss/cli/model" ) +// noOpBrowserOpener is a no-op browser opener for testing +func noOpBrowserOpener(url string) error { + return nil +} + func TestNewPreviewCommand(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockClient := mock_client.NewMockAPI(ctrl) var buf bytes.Buffer - cmd := collection.NewPreviewCommand(mockClient, &buf) + cmd := collection.NewPreviewCommandWithOpener(mockClient, &buf, noOpBrowserOpener) use := "preview " short := "Preview a collection in the browser" @@ -40,7 +45,7 @@ func TestPreviewCommandRequiresCollectionID(t *testing.T) { mockClient := mock_client.NewMockAPI(ctrl) var buf bytes.Buffer - cmd := collection.NewPreviewCommand(mockClient, &buf) + cmd := collection.NewPreviewCommandWithOpener(mockClient, &buf, noOpBrowserOpener) err := cmd.RunE(cmd, []string{}) if err == nil { @@ -59,7 +64,7 @@ func TestPreviewCommandRequiresNonEmptyCollectionID(t *testing.T) { mockClient := mock_client.NewMockAPI(ctrl) var buf bytes.Buffer - cmd := collection.NewPreviewCommand(mockClient, &buf) + cmd := collection.NewPreviewCommandWithOpener(mockClient, &buf, noOpBrowserOpener) err := cmd.RunE(cmd, []string{""}) if err == nil { @@ -93,16 +98,13 @@ func TestPreviewCommandCallsGetCollection(t *testing.T) { var buf bytes.Buffer writer := bufio.NewWriter(&buf) - cmd := collection.NewPreviewCommand(mockClient, writer) + cmd := collection.NewPreviewCommandWithOpener(mockClient, writer, noOpBrowserOpener) - // Note: This will attempt to open the browser, but we're just testing that - // the GetCollection call is made and no error is returned err := cmd.RunE(cmd, []string{testCollectionID}) writer.Flush() - // We don't fail on browser.OpenURL errors in CI environments - if err != nil && !strings.Contains(err.Error(), "browser") { - t.Fatalf("expected no error (or browser error), got: %v", err) + if err != nil { + t.Fatalf("expected no error, got: %v", err) } } @@ -119,7 +121,7 @@ func TestPreviewCommandReturnsErrorOnClientError(t *testing.T) { var buf bytes.Buffer writer := bufio.NewWriter(&buf) - cmd := collection.NewPreviewCommand(mockClient, writer) + cmd := collection.NewPreviewCommandWithOpener(mockClient, writer, noOpBrowserOpener) err := cmd.RunE(cmd, []string{testCollectionID}) writer.Flush() @@ -150,7 +152,7 @@ func TestPreviewCommandHandlesFeatureNotEnabledError(t *testing.T) { var buf bytes.Buffer writer := bufio.NewWriter(&buf) - cmd := collection.NewPreviewCommand(mockClient, writer) + cmd := collection.NewPreviewCommandWithOpener(mockClient, writer, noOpBrowserOpener) err := cmd.RunE(cmd, []string{testCollectionID}) writer.Flush() @@ -183,12 +185,15 @@ func TestPreviewCommandOutputContainsURL(t *testing.T) { var buf bytes.Buffer writer := bufio.NewWriter(&buf) - cmd := collection.NewPreviewCommand(mockClient, writer) + cmd := collection.NewPreviewCommandWithOpener(mockClient, writer, noOpBrowserOpener) - // Run the command - may fail to open browser in CI - _ = cmd.RunE(cmd, []string{testCollectionID}) + err := cmd.RunE(cmd, []string{testCollectionID}) writer.Flush() + if err != nil { + t.Fatalf("expected no error, got: %v", err) + } + output := buf.String() // Check that the output contains the expected URL components From e7c374717fad70512bcdc37a06c09a9aa09a53c2 Mon Sep 17 00:00:00 2001 From: Keir Lavelle Date: Sun, 1 Feb 2026 21:05:46 +0000 Subject: [PATCH 3/5] fix(DCP-2356): Replaced Args: cobra.MinimumNArgs(1) with a custom Args validator in collection preview command --- cmd/collection/preview.go | 17 ++++++++--------- cmd/collection/preview_test.go | 6 ++++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/cmd/collection/preview.go b/cmd/collection/preview.go index d8db72a..5a99f1b 100644 --- a/cmd/collection/preview.go +++ b/cmd/collection/preview.go @@ -39,8 +39,13 @@ func NewPreviewCommandWithOpener(c client.API, w io.Writer, browserOpener Browse opts.BrowserOpener = browserOpener cmd := &cobra.Command{ - Use: "preview ", - Args: cobra.MinimumNArgs(1), + Use: "preview ", + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < 1 || args[0] == "" { + return errors.New("please provide a collection ID") + } + return nil + }, Short: "Preview a collection in the browser", Long: `Preview a collection in the browser @@ -52,13 +57,7 @@ Preview a collection in the browser $ prolific collection preview 123456789 `, RunE: func(cmd *cobra.Command, args []string) error { - opts.Args = args - - if len(opts.Args) < 1 || opts.Args[0] == "" { - return errors.New("please provide a collection ID") - } - - collectionID := opts.Args[0] + collectionID := args[0] // Fetch collection to validate access _, err := c.GetCollection(collectionID) diff --git a/cmd/collection/preview_test.go b/cmd/collection/preview_test.go index c047a3c..88787a2 100644 --- a/cmd/collection/preview_test.go +++ b/cmd/collection/preview_test.go @@ -47,7 +47,8 @@ func TestPreviewCommandRequiresCollectionID(t *testing.T) { var buf bytes.Buffer cmd := collection.NewPreviewCommandWithOpener(mockClient, &buf, noOpBrowserOpener) - err := cmd.RunE(cmd, []string{}) + // Test the Args validator directly + err := cmd.Args(cmd, []string{}) if err == nil { t.Fatalf("expected error for missing collection ID, got nil") } @@ -66,7 +67,8 @@ func TestPreviewCommandRequiresNonEmptyCollectionID(t *testing.T) { var buf bytes.Buffer cmd := collection.NewPreviewCommandWithOpener(mockClient, &buf, noOpBrowserOpener) - err := cmd.RunE(cmd, []string{""}) + // Test the Args validator directly + err := cmd.Args(cmd, []string{""}) if err == nil { t.Fatalf("expected error for empty collection ID, got nil") } From 6b54f722bc491d563690e3280cac827019f5cdfe Mon Sep 17 00:00:00 2001 From: Keir Lavelle Date: Sun, 1 Feb 2026 21:14:18 +0000 Subject: [PATCH 4/5] fix(DCP-2356): Correct Args access --- cmd/collection/preview.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/collection/preview.go b/cmd/collection/preview.go index 5a99f1b..33fe5d4 100644 --- a/cmd/collection/preview.go +++ b/cmd/collection/preview.go @@ -57,7 +57,8 @@ Preview a collection in the browser $ prolific collection preview 123456789 `, RunE: func(cmd *cobra.Command, args []string) error { - collectionID := args[0] + opts.Args = args + collectionID := opts.Args[0] // Fetch collection to validate access _, err := c.GetCollection(collectionID) From 9ee8ee8754a9b966fc322465ea33784f51f3e5ab Mon Sep 17 00:00:00 2001 From: Keir Lavelle Date: Mon, 2 Feb 2026 09:46:57 +0000 Subject: [PATCH 5/5] fix(DCP-2356): Provide feedback when browser opening fails + more consistent error messaging --- cmd/collection/preview.go | 6 ++++-- cmd/collection/preview_test.go | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/collection/preview.go b/cmd/collection/preview.go index 33fe5d4..001d635 100644 --- a/cmd/collection/preview.go +++ b/cmd/collection/preview.go @@ -67,7 +67,7 @@ $ prolific collection preview 123456789 ui.RenderFeatureAccessMessage(FeatureNameAITBCollection, FeatureContactURLAITBCollection) return nil } - return fmt.Errorf("error: %s", err.Error()) + return fmt.Errorf("failed to get collection: %s", err.Error()) } // Build the preview URL and display it @@ -79,7 +79,9 @@ $ prolific collection preview 123456789 // Attempt to open the browser - don't fail if it doesn't work // (e.g., in headless/CI environments) if opts.BrowserOpener != nil { - _ = opts.BrowserOpener(previewURL) + if err := opts.BrowserOpener(previewURL); err != nil { + fmt.Fprintln(w, "(Browser did not open automatically - use the URL above)") + } } return nil diff --git a/cmd/collection/preview_test.go b/cmd/collection/preview_test.go index 88787a2..0efa7ac 100644 --- a/cmd/collection/preview_test.go +++ b/cmd/collection/preview_test.go @@ -132,7 +132,7 @@ func TestPreviewCommandReturnsErrorOnClientError(t *testing.T) { t.Fatal("expected error, got nil") } - expected := "error: collection not found" + expected := "failed to get collection: collection not found" if err.Error() != expected { t.Fatalf("expected error %q, got %q", expected, err.Error()) }