diff --git a/cmd/datastore/query_test.go b/cmd/datastore/query_test.go index 0f704327..37b3d2b1 100644 --- a/cmd/datastore/query_test.go +++ b/cmd/datastore/query_test.go @@ -465,3 +465,55 @@ func prepareExportMockData(cm *shared.ClientsMock, numberOfItems int, maxItemsTo } return data, nil } + +func Test_getExpressionPatterns(t *testing.T) { + tests := map[string]struct { + expression string + wantAttrs int + wantVals int + }{ + "expression with attributes and values": { + expression: "#name = :name AND #status = :status", + wantAttrs: 2, + wantVals: 2, + }, + "empty expression": { + expression: "", + wantAttrs: 0, + wantVals: 0, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + attrs, vals := getExpressionPatterns(tc.expression) + assert.Len(t, attrs, tc.wantAttrs) + assert.Len(t, vals, tc.wantVals) + }) + } +} + +func Test_mapAttributeFlag(t *testing.T) { + tests := map[string]struct { + flag string + wantErr bool + }{ + "valid JSON": { + flag: `{"#name":"name"}`, + }, + "invalid JSON": { + flag: `not json`, + wantErr: true, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result, err := mapAttributeFlag(tc.flag) + if tc.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotNil(t, result) + } + }) + } +} diff --git a/internal/api/app_test.go b/internal/api/app_test.go index f1e84015..873f7b49 100644 --- a/internal/api/app_test.go +++ b/internal/api/app_test.go @@ -34,6 +34,44 @@ import ( "github.com/stretchr/testify/require" ) +func Test_Client_ConnectionsOpen(t *testing.T) { + tests := map[string]struct { + httpResponseJSON string + wantErr bool + errMessage string + expectedURL string + }{ + "OK result": { + httpResponseJSON: `{"ok":true,"url":"wss://example.com/ws"}`, + expectedURL: "wss://example.com/ws", + }, + "Error result": { + httpResponseJSON: `{"ok":false,"error":"token_revoked"}`, + wantErr: true, + errMessage: "token_revoked", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appConnectionsOpenMethod, + Response: tc.httpResponseJSON, + }) + defer teardown() + + result, err := c.ConnectionsOpen(ctx, "token") + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + require.Equal(t, tc.expectedURL, result.URL) + } + }) + } +} + func TestClient_CreateApp_Ok(t *testing.T) { ctx := slackcontext.MockContext(t.Context()) c, teardown := NewFakeClient(t, FakeClientParams{ @@ -80,6 +118,42 @@ func TestClient_ExportAppManifest_CommonErrors(t *testing.T) { }) } +func Test_Client_GetAppStatus(t *testing.T) { + tests := map[string]struct { + httpResponseJSON string + wantErr bool + errMessage string + }{ + "OK result": { + httpResponseJSON: `{"ok":true,"apps":[{"app_id":"A123","status":"installed"}]}`, + }, + "Error result": { + httpResponseJSON: `{"ok":false,"error":"invalid_app"}`, + wantErr: true, + errMessage: "invalid_app", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appStatusMethod, + Response: tc.httpResponseJSON, + }) + defer teardown() + + result, err := c.GetAppStatus(ctx, "token", []string{"A123"}, "T123") + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + require.NotNil(t, result) + } + }) + } +} + func TestClient_UpdateApp_OK(t *testing.T) { ctx := slackcontext.MockContext(t.Context()) c, teardown := NewFakeClient(t, FakeClientParams{ diff --git a/internal/api/datastore_test.go b/internal/api/datastore_test.go index c1126118..c1e8c4cb 100644 --- a/internal/api/datastore_test.go +++ b/internal/api/datastore_test.go @@ -490,3 +490,192 @@ func TestClient_AppsDatastoreGet(t *testing.T) { }) } } + +func Test_Client_AppsDatastoreBulkPut(t *testing.T) { + tests := map[string]struct { + request types.AppDatastoreBulkPut + httpResponseJSON string + statusCode int + wantErr bool + errMessage string + }{ + "success": { + request: types.AppDatastoreBulkPut{ + Datastore: "my_ds", + App: "A1", + Items: []map[string]interface{}{{"id": "1", "name": "test"}}, + }, + httpResponseJSON: `{"ok":true,"datastore":"my_ds"}`, + }, + "api_error": { + request: types.AppDatastoreBulkPut{ + Datastore: "my_ds", + App: "A1", + Items: []map[string]interface{}{{"id": "1"}}, + }, + httpResponseJSON: `{"ok":false,"error":"datastore_error"}`, + wantErr: true, + errMessage: "datastore_error", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appDatastoreBulkPutMethod, + Response: tc.httpResponseJSON, + StatusCode: tc.statusCode, + }) + defer teardown() + _, err := c.AppsDatastoreBulkPut(ctx, "token", tc.request) + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +func Test_Client_AppsDatastoreCount(t *testing.T) { + tests := map[string]struct { + request types.AppDatastoreCount + httpResponseJSON string + statusCode int + wantCount int + wantErr bool + errMessage string + }{ + "success": { + request: types.AppDatastoreCount{ + Datastore: "my_ds", + App: "A1", + }, + httpResponseJSON: `{"ok":true,"datastore":"my_ds","count":42}`, + wantCount: 42, + }, + "api_error": { + request: types.AppDatastoreCount{ + Datastore: "my_ds", + App: "A1", + }, + httpResponseJSON: `{"ok":false,"error":"datastore_error"}`, + wantErr: true, + errMessage: "datastore_error", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appDatastoreCountMethod, + Response: tc.httpResponseJSON, + StatusCode: tc.statusCode, + }) + defer teardown() + got, err := c.AppsDatastoreCount(ctx, "token", tc.request) + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + require.Equal(t, tc.wantCount, got.Count) + } + }) + } +} + +func Test_Client_AppsDatastoreBulkDelete(t *testing.T) { + tests := map[string]struct { + request types.AppDatastoreBulkDelete + httpResponseJSON string + statusCode int + wantErr bool + errMessage string + }{ + "success": { + request: types.AppDatastoreBulkDelete{ + Datastore: "my_ds", + App: "A1", + IDs: []string{"id1", "id2"}, + }, + httpResponseJSON: `{"ok":true}`, + }, + "api_error": { + request: types.AppDatastoreBulkDelete{ + Datastore: "my_ds", + App: "A1", + IDs: []string{"id1"}, + }, + httpResponseJSON: `{"ok":false,"error":"not_found"}`, + wantErr: true, + errMessage: "not_found", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appDatastoreBulkDeleteMethod, + Response: tc.httpResponseJSON, + StatusCode: tc.statusCode, + }) + defer teardown() + _, err := c.AppsDatastoreBulkDelete(ctx, "token", tc.request) + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} + +func Test_Client_AppsDatastoreBulkGet(t *testing.T) { + tests := map[string]struct { + request types.AppDatastoreBulkGet + httpResponseJSON string + statusCode int + wantErr bool + errMessage string + }{ + "success": { + request: types.AppDatastoreBulkGet{ + Datastore: "my_ds", + App: "A1", + IDs: []string{"id1", "id2"}, + }, + httpResponseJSON: `{"ok":true,"datastore":"my_ds","items":[{"id":"id1","name":"test"}]}`, + }, + "api_error": { + request: types.AppDatastoreBulkGet{ + Datastore: "my_ds", + App: "A1", + IDs: []string{"id1"}, + }, + httpResponseJSON: `{"ok":false,"error":"not_found"}`, + wantErr: true, + errMessage: "not_found", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ctx := slackcontext.MockContext(t.Context()) + c, teardown := NewFakeClient(t, FakeClientParams{ + ExpectedMethod: appDatastoreBulkGetMethod, + Response: tc.httpResponseJSON, + StatusCode: tc.statusCode, + }) + defer teardown() + _, err := c.AppsDatastoreBulkGet(ctx, "token", tc.request) + if tc.wantErr { + require.Error(t, err) + require.Contains(t, err.Error(), tc.errMessage) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/internal/config/flags_test.go b/internal/config/flags_test.go index 3db25406..9be5d929 100644 --- a/internal/config/flags_test.go +++ b/internal/config/flags_test.go @@ -24,6 +24,91 @@ import ( "github.com/stretchr/testify/assert" ) +func Test_SetFlags(t *testing.T) { + fs := slackdeps.NewFsMock() + os := slackdeps.NewOsMock() + config := NewConfig(fs, os) + cmd := &cobra.Command{} + cmd.Flags().String("test-flag", "default", "a test flag") + + config.SetFlags(cmd) + assert.NotNil(t, config.Flags) + f := config.Flags.Lookup("test-flag") + assert.NotNil(t, f) + assert.Equal(t, "default", f.DefValue) +} + +func Test_InitializeGlobalFlags(t *testing.T) { + fs := slackdeps.NewFsMock() + os := slackdeps.NewOsMock() + config := NewConfig(fs, os) + cmd := &cobra.Command{} + + config.InitializeGlobalFlags(cmd) + + tests := map[string]struct { + longform string + shorthand string + hidden bool + }{ + "apihost": { + longform: "apihost", + hidden: true, + }, + "app": { + longform: "app", + shorthand: "a", + }, + "config-dir": { + longform: "config-dir", + }, + "experiment": { + longform: "experiment", + }, + "force": { + longform: "force", + shorthand: "f", + }, + "no-color": { + longform: "no-color", + }, + "runtime": { + longform: "runtime", + shorthand: "r", + hidden: true, + }, + "skip-update": { + longform: "skip-update", + shorthand: "s", + }, + "slackdev": { + longform: "slackdev", + hidden: true, + }, + "team": { + longform: "team", + shorthand: "w", + }, + "token": { + longform: "token", + }, + "verbose": { + longform: "verbose", + shorthand: "v", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + f := cmd.PersistentFlags().Lookup(tc.longform) + assert.NotNil(t, f, "flag %s should be registered", tc.longform) + if tc.shorthand != "" { + assert.Equal(t, tc.shorthand, f.Shorthand, "flag %s shorthand mismatch", tc.longform) + } + assert.Equal(t, tc.hidden, f.Hidden, "flag %s hidden mismatch", tc.longform) + }) + } +} + func TestDeprecatedFlagSubstitutions(t *testing.T) { tests := map[string]struct { expectedWarnings []string diff --git a/internal/deputil/url_test.go b/internal/deputil/url_test.go index e26fee9f..f69b55e4 100644 --- a/internal/deputil/url_test.go +++ b/internal/deputil/url_test.go @@ -53,6 +53,30 @@ func Test_URLChecker(t *testing.T) { httpClientMock.On("Head", mock.Anything).Return(nil, fmt.Errorf("HTTPClient error")) }, }, + "Returns an empty string for HTTP 500 Internal Server Error": { + url: "https://example.com/server-error", + expectedURL: "", + setupHTTPClientMock: func(httpClientMock *slackhttp.HTTPClientMock) { + res := slackhttp.MockHTTPResponse(http.StatusInternalServerError, "Internal Server Error") + httpClientMock.On("Head", mock.Anything).Return(res, nil) + }, + }, + "Returns an empty string for HTTP 301 redirect": { + url: "https://example.com/redirect", + expectedURL: "", + setupHTTPClientMock: func(httpClientMock *slackhttp.HTTPClientMock) { + res := slackhttp.MockHTTPResponse(http.StatusMovedPermanently, "Moved") + httpClientMock.On("Head", mock.Anything).Return(res, nil) + }, + }, + "Returns an empty string for HTTP 403 Forbidden": { + url: "https://example.com/forbidden", + expectedURL: "", + setupHTTPClientMock: func(httpClientMock *slackhttp.HTTPClientMock) { + res := slackhttp.MockHTTPResponse(http.StatusForbidden, "Forbidden") + httpClientMock.On("Head", mock.Anything).Return(res, nil) + }, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) {