Skip to content

Commit 8ec404c

Browse files
authored
feat: 'app link' supports run-on-slack apps with local manifest source (#56)
* feat: update 'app link' to support run-on-slack apps with local manifest src * test: fix failing 'project init' test * test: update tests with latest ctx changes * refactor: simplify the manifest update conditional
1 parent 4cfcf8f commit 8ec404c

3 files changed

Lines changed: 176 additions & 7 deletions

File tree

cmd/app/link.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ func LinkCommandRunE(ctx context.Context, clients *shared.ClientFactory, app *ty
115115
// explaining how to link apps, in case the user declines.
116116
func LinkAppHeaderSection(ctx context.Context, clients *shared.ClientFactory, shouldConfirm bool) {
117117
var secondaryText = []string{
118-
"Add an existing app created on app settings",
118+
"Add an existing app from app settings",
119119
"Find your existing apps at: " + style.Underline("https://api.slack.com/apps"),
120120
}
121121

@@ -156,10 +156,14 @@ func LinkExistingApp(ctx context.Context, clients *shared.ClientFactory, app *ty
156156
}
157157
}
158158

159-
// Confirm to update manifest source to remote
159+
// Confirm to update manifest source to remote.
160+
// - Update the manifest source to remote when its a GBP project with a local manifest.
161+
// - Do not update manifest source for ROSI projects, because they can only be local manifests.
160162
manifestSource, err := clients.Config.ProjectConfig.GetManifestSource(ctx)
163+
isManifestSourceRemote := manifestSource.Equals(config.MANIFEST_SOURCE_REMOTE)
164+
isSlackHostedProject := cmdutil.IsSlackHostedProject(ctx, clients) == nil
161165

162-
if !manifestSource.Equals(config.MANIFEST_SOURCE_REMOTE) || err != nil {
166+
if err != nil || (!isManifestSourceRemote && !isSlackHostedProject) {
163167
// When undefined, the default is config.MANIFEST_SOURCE_LOCAL
164168
if !manifestSource.Exists() {
165169
manifestSource = config.MANIFEST_SOURCE_LOCAL

cmd/app/link_test.go

Lines changed: 163 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"testing"
2121

2222
"github.com/slackapi/slack-cli/internal/api"
23+
"github.com/slackapi/slack-cli/internal/app"
2324
"github.com/slackapi/slack-cli/internal/config"
2425
"github.com/slackapi/slack-cli/internal/iostreams"
2526
"github.com/slackapi/slack-cli/internal/shared"
@@ -430,7 +431,7 @@ func Test_Apps_Link(t *testing.T) {
430431
CmdArgs: []string{},
431432
ExpectedError: slackerror.New(slackerror.ErrAppNotFound),
432433
},
433-
"accept manifest source prompt and saves information about the provided deployed app": {
434+
"accepting manifest source prompt should save information about the provided deployed app": {
434435
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
435436
cm.AuthInterface.On("Auths", mock.Anything).Return([]types.SlackAuth{
436437
mockLinkSlackAuth2,
@@ -503,7 +504,7 @@ func Test_Apps_Link(t *testing.T) {
503504
)
504505
},
505506
},
506-
"decline manifest source prompt should not link app": {
507+
"declining manifest source prompt should not link app": {
507508
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
508509
cm.AddDefaultMocks()
509510
setupAppLinkCommandMocks(t, ctx, cm, cf)
@@ -537,6 +538,159 @@ func Test_Apps_Link(t *testing.T) {
537538
require.Len(t, apps, 0)
538539
},
539540
},
541+
"manifest source prompt should not display for Run-on-Slack apps with local manifest source": {
542+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
543+
cm.AuthInterface.On("Auths", mock.Anything).Return([]types.SlackAuth{
544+
mockLinkSlackAuth1,
545+
mockLinkSlackAuth2,
546+
}, nil)
547+
cm.AddDefaultMocks()
548+
setupAppLinkCommandMocks(t, ctx, cm, cf)
549+
// Set manifest source to local
550+
if err := cm.Config.ProjectConfig.SetManifestSource(ctx, config.MANIFEST_SOURCE_LOCAL); err != nil {
551+
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
552+
}
553+
// Mock manifest for Run-on-Slack app
554+
manifestMock := &app.ManifestMockObject{}
555+
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{
556+
AppManifest: types.AppManifest{
557+
Settings: &types.AppSettings{
558+
FunctionRuntime: types.SLACK_HOSTED,
559+
},
560+
},
561+
}, nil)
562+
cf.AppClient().Manifest = manifestMock
563+
cm.IO.On("SelectPrompt",
564+
mock.Anything,
565+
"Select the existing app team",
566+
mock.Anything,
567+
mock.Anything,
568+
mock.Anything,
569+
).Return(iostreams.SelectPromptResponse{
570+
Flag: true,
571+
Option: mockLinkSlackAuth1.TeamDomain,
572+
}, nil)
573+
cm.IO.On("InputPrompt",
574+
mock.Anything,
575+
"Enter the existing app ID",
576+
mock.Anything,
577+
).Return(mockLinkAppID1, nil)
578+
cm.IO.On("SelectPrompt",
579+
mock.Anything,
580+
"Choose the app environment",
581+
mock.Anything,
582+
mock.Anything,
583+
mock.Anything,
584+
).Return(iostreams.SelectPromptResponse{
585+
Flag: true,
586+
Option: "deployed",
587+
}, nil)
588+
cm.ApiInterface.On(
589+
"GetAppStatus",
590+
mock.Anything,
591+
mockLinkSlackAuth1.Token,
592+
[]string{mockLinkAppID1},
593+
mockLinkSlackAuth1.TeamID,
594+
).Return(api.GetAppStatusResult{}, nil)
595+
},
596+
CmdArgs: []string{},
597+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
598+
expectedApp := types.App{
599+
AppID: mockLinkAppID1,
600+
TeamDomain: mockLinkSlackAuth1.TeamDomain,
601+
TeamID: mockLinkSlackAuth1.TeamID,
602+
EnterpriseID: mockLinkSlackAuth1.EnterpriseID,
603+
}
604+
actualApp, err := cm.AppClient.GetDeployed(
605+
ctx,
606+
mockLinkSlackAuth1.TeamID,
607+
)
608+
require.NoError(t, err)
609+
assert.Equal(t, expectedApp, actualApp)
610+
// Assert manifest confirmation prompt was not displayed
611+
cm.IO.AssertNotCalled(t, "ConfirmPrompt",
612+
mock.Anything,
613+
LinkAppManifestSourceConfirmPromptText,
614+
mock.Anything,
615+
)
616+
},
617+
},
618+
"manifest source prompt should display for GBP apps with local manifest source": {
619+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
620+
cm.AuthInterface.On("Auths", mock.Anything).Return([]types.SlackAuth{
621+
mockLinkSlackAuth1,
622+
mockLinkSlackAuth2,
623+
}, nil)
624+
cm.AddDefaultMocks()
625+
setupAppLinkCommandMocks(t, ctx, cm, cf)
626+
// Set manifest source to local
627+
if err := cm.Config.ProjectConfig.SetManifestSource(ctx, config.MANIFEST_SOURCE_LOCAL); err != nil {
628+
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
629+
}
630+
// Mock manifest for Run-on-Slack app
631+
manifestMock := &app.ManifestMockObject{}
632+
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil)
633+
cf.AppClient().Manifest = manifestMock
634+
cm.IO.On("ConfirmPrompt",
635+
mock.Anything,
636+
LinkAppManifestSourceConfirmPromptText,
637+
mock.Anything,
638+
).Return(true, nil)
639+
cm.IO.On("SelectPrompt",
640+
mock.Anything,
641+
"Select the existing app team",
642+
mock.Anything,
643+
mock.Anything,
644+
mock.Anything,
645+
).Return(iostreams.SelectPromptResponse{
646+
Flag: true,
647+
Option: mockLinkSlackAuth1.TeamDomain,
648+
}, nil)
649+
cm.IO.On("InputPrompt",
650+
mock.Anything,
651+
"Enter the existing app ID",
652+
mock.Anything,
653+
).Return(mockLinkAppID1, nil)
654+
cm.IO.On("SelectPrompt",
655+
mock.Anything,
656+
"Choose the app environment",
657+
mock.Anything,
658+
mock.Anything,
659+
mock.Anything,
660+
).Return(iostreams.SelectPromptResponse{
661+
Flag: true,
662+
Option: "deployed",
663+
}, nil)
664+
cm.ApiInterface.On(
665+
"GetAppStatus",
666+
mock.Anything,
667+
mockLinkSlackAuth1.Token,
668+
[]string{mockLinkAppID1},
669+
mockLinkSlackAuth1.TeamID,
670+
).Return(api.GetAppStatusResult{}, nil)
671+
},
672+
CmdArgs: []string{},
673+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
674+
expectedApp := types.App{
675+
AppID: mockLinkAppID1,
676+
TeamDomain: mockLinkSlackAuth1.TeamDomain,
677+
TeamID: mockLinkSlackAuth1.TeamID,
678+
EnterpriseID: mockLinkSlackAuth1.EnterpriseID,
679+
}
680+
actualApp, err := cm.AppClient.GetDeployed(
681+
ctx,
682+
mockLinkSlackAuth1.TeamID,
683+
)
684+
require.NoError(t, err)
685+
assert.Equal(t, expectedApp, actualApp)
686+
// Assert manifest confirmation prompt was displayed
687+
cm.IO.AssertCalled(t, "ConfirmPrompt",
688+
mock.Anything,
689+
LinkAppManifestSourceConfirmPromptText,
690+
mock.Anything,
691+
)
692+
},
693+
},
540694
}, func(clients *shared.ClientFactory) *cobra.Command {
541695
clients.SDKConfig.WorkingDirectory = "."
542696
return NewLinkCommand(clients)
@@ -552,7 +706,7 @@ func Test_Apps_LinkAppHeaderSection(t *testing.T) {
552706
"When shouldConfirm is false": {
553707
shouldConfirm: false,
554708
expectedOutputs: []string{
555-
"Add an existing app created on app settings",
709+
"Add an existing app from app settings",
556710
"Find your existing apps at: https://api.slack.com/apps",
557711
},
558712
unexpectedOutputs: []string{
@@ -562,7 +716,7 @@ func Test_Apps_LinkAppHeaderSection(t *testing.T) {
562716
"When shouldConfirm is true": {
563717
shouldConfirm: true,
564718
expectedOutputs: []string{
565-
"Add an existing app created on app settings",
719+
"Add an existing app from app settings",
566720
"Find your existing apps at: https://api.slack.com/apps",
567721
"Manually add apps later with",
568722
},
@@ -609,4 +763,9 @@ func setupAppLinkCommandMocks(t *testing.T, ctx context.Context, cm *shared.Clie
609763
if err := cm.Config.ProjectConfig.SetManifestSource(ctx, config.MANIFEST_SOURCE_REMOTE); err != nil {
610764
require.FailNow(t, fmt.Sprintf("Failed to set the manifest source in the memory-based file system: %s", err))
611765
}
766+
767+
// Mock manifest
768+
manifestMock := &app.ManifestMockObject{}
769+
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil)
770+
cf.AppClient().Manifest = manifestMock
612771
}

cmd/project/init_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/slackapi/slack-cli/cmd/app"
2424
"github.com/slackapi/slack-cli/internal/api"
25+
internalApp "github.com/slackapi/slack-cli/internal/app"
2526
"github.com/slackapi/slack-cli/internal/iostreams"
2627
"github.com/slackapi/slack-cli/internal/shared"
2728
"github.com/slackapi/slack-cli/internal/shared/types"
@@ -237,4 +238,9 @@ func setupProjectInitCommandMocks(t *testing.T, ctx context.Context, cm *shared.
237238
if err := cm.Fs.MkdirAll(filepath.Dir(projectDirPath), 0755); err != nil {
238239
require.FailNow(t, fmt.Sprintf("Failed to create the directory %s in the memory-based file system", projectDirPath))
239240
}
241+
242+
// Mock manifest
243+
manifestMock := &internalApp.ManifestMockObject{}
244+
manifestMock.On("GetManifestLocal", mock.Anything, mock.Anything, mock.Anything).Return(types.SlackYaml{}, nil)
245+
cf.AppClient().Manifest = manifestMock
240246
}

0 commit comments

Comments
 (0)