Repository: knative/client (go)

Issue:
Context sensitive Bash completion
<!-- If you need to report a security issue with Knative, send an email to knative-security@googlegroups.com. -->

### Feature request

`kn completion` is currently based on the auto generation of the bash/zsh completion code by cobra. It lacks the the possibility to complete on context sensitive information likes service or revision names, something that e.g. kubectl allows. 

We should extend the completion code to allow completion on those resources. The way how `kubectl` does it could be taken as an inspiration.
<!-- Please describe the feature request and why you would like to have it -->

### UI Example

<!-- If this is about a new command or command line options, please let us know how you would add it to the "kn" UI (in the code block below). If this is not applicable for your feature, delete this section. -->

```
kn service describe my<TAB>
```

would complete to "myservice" if there would be a Knative service installed that is called "myservice".
<!--
Optional Classifications, remove the '> ' to select:
> /kind good-first-issue
> /kind proposal
> /kind doc
> /kind cleanup
-->

Here are the smaller issues tracked by this issue:

- [x] #1374 
- [x] #1547 
- [x] #1559
- [x] #1560
- [x] #1561
- [ ] #1562

Pull request description:
Adding completion for remaining Eventing resources
## Description
Context specific autocompletion for eventing resources
<!-- Please add a short summary about what the pull request is going to bring on the table -->

## Changes

<!-- Please add list of more detailed changes. These changes should be reflected also in the commit messages -->

* Added the ValidArgs function for the following resources
    * triggers
    * source:
        * ping
        * sinkbinding
        * apiserver
        * container
    * channel
    * subscription  

## Reference

<!-- Please add the corresponding issue number which this pull request is about to fix -->
Fixes #1373 

<!--
Please add an entry to CHANGELOG.adoc file, too, as part of your Pull Request.

In the following cases, add a short description of PR to the unreleased section in CHANGELOG.adoc:

- 🎁 New feature
- 🐛 Bug fix
- ✨ Feature Update
- 🐣 Refactoring
- 🗑️ Remove feature or internal logic

See other entries in CHANGELOG.adoc as an example for how to add the entry, including a reference to the corresponding issue/PR

PLEASE DON'T ADD THAT LINE HERE IN THE PULL-REQUEST DESCRIPTION BUT DIRECTLY IN CHANGELOG.ADOC AND ADD CHANGELOG.ADOC AS PART OF YOUR PULL-REQUEST.
-->

<!--
To automatically lint go code in this pull request uncomment the line below. You get feedback as comments on your pull request then -->

<!--
/lint
-->


Patch:
diff --git a/pkg/kn/commands/channel/delete.go b/pkg/kn/commands/channel/delete.go
index 3c22b5db..ebb3eb5d 100644
--- a/pkg/kn/commands/channel/delete.go
+++ b/pkg/kn/commands/channel/delete.go
@@ -30,6 +30,7 @@ func NewChannelDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a channel 'pipe'
   kn channel delete pipe`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn channel delete' requires the channel name as single argument")
diff --git a/pkg/kn/commands/channel/describe.go b/pkg/kn/commands/channel/describe.go
index 21661340..38e03cda 100644
--- a/pkg/kn/commands/channel/describe.go
+++ b/pkg/kn/commands/channel/describe.go
@@ -43,9 +43,10 @@ func NewChannelDescribeCommand(p *commands.KnParams) *cobra.Command {
 	machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
 
 	cmd := &cobra.Command{
-		Use:     "describe NAME",
-		Short:   "Show details of a channel",
-		Example: describeExample,
+		Use:               "describe NAME",
+		Short:             "Show details of a channel",
+		Example:           describeExample,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn channel describe' requires the channel name given as single argument")
diff --git a/pkg/kn/commands/completion_helper.go b/pkg/kn/commands/completion_helper.go
index 90a3c62a..d1816a5d 100644
--- a/pkg/kn/commands/completion_helper.go
+++ b/pkg/kn/commands/completion_helper.go
@@ -29,11 +29,18 @@ type completionConfig struct {
 
 var (
 	resourceToFuncMap = map[string]func(config *completionConfig) []string{
-		"broker":   completeBroker,
-		"domain":   completeDomain,
-		"revision": completeRevision,
-		"route":    completeRoute,
-		"service":  completeService,
+		"apiserver":    completeApiserverSource,
+		"binding":      completeBindingSource,
+		"broker":       completeBroker,
+		"channel":      completeChannel,
+		"container":    completeContainerSource,
+		"domain":       completeDomain,
+		"ping":         completePingSource,
+		"revision":     completeRevision,
+		"route":        completeRoute,
+		"service":      completeService,
+		"subscription": completeSubscription,
+		"trigger":      completeTrigger,
 	}
 )
 
@@ -231,3 +238,192 @@ func completeDomain(config *completionConfig) (suggestions []string) {
 	}
 	return
 }
+
+func completeTrigger(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+	client, err := config.params.NewEventingClient(namespace)
+	if err != nil {
+		return
+	}
+	triggerList, err := client.ListTriggers(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range triggerList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completeContainerSource(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+	client, err := config.params.NewSourcesClient(namespace)
+	if err != nil {
+		return
+	}
+	containerSourceList, err := client.ContainerSourcesClient().ListContainerSources(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range containerSourceList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completeApiserverSource(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+	client, err := config.params.NewSourcesClient(namespace)
+	if err != nil {
+		return
+	}
+	apiServerSourceList, err := client.APIServerSourcesClient().ListAPIServerSource(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range apiServerSourceList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completeBindingSource(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+	client, err := config.params.NewSourcesClient(namespace)
+	if err != nil {
+		return
+	}
+	bindingList, err := client.SinkBindingClient().ListSinkBindings(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range bindingList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completePingSource(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+
+	client, err := config.params.NewSourcesV1beta2Client(namespace)
+	if err != nil {
+		return
+	}
+	pingSourcesClient := client.PingSourcesClient()
+
+	pingSourceList, err := pingSourcesClient.ListPingSource(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range pingSourceList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completeChannel(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+
+	client, err := config.params.NewMessagingClient(namespace)
+	if err != nil {
+		return
+	}
+
+	channelList, err := client.ChannelsClient().ListChannel(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range channelList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
+
+func completeSubscription(config *completionConfig) (suggestions []string) {
+	suggestions = make([]string, 0)
+	if len(config.args) != 0 {
+		return
+	}
+	namespace, err := config.params.GetNamespace(config.command)
+	if err != nil {
+		return
+	}
+
+	client, err := config.params.NewMessagingClient(namespace)
+	if err != nil {
+		return
+	}
+
+	subscriptionList, err := client.SubscriptionsClient().ListSubscription(config.command.Context())
+	if err != nil {
+		return
+	}
+	for _, sug := range subscriptionList.Items {
+		if !strings.HasPrefix(sug.Name, config.toComplete) {
+			continue
+		}
+		suggestions = append(suggestions, sug.Name)
+	}
+	return
+}
diff --git a/pkg/kn/commands/source/apiserver/delete.go b/pkg/kn/commands/source/apiserver/delete.go
index 4490ea30..be7f2fb7 100644
--- a/pkg/kn/commands/source/apiserver/delete.go
+++ b/pkg/kn/commands/source/apiserver/delete.go
@@ -31,6 +31,7 @@ func NewAPIServerDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete an ApiServerSource 'k8sevents' in default namespace
   kn source apiserver delete k8sevents`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("requires the name of the source as single argument")
diff --git a/pkg/kn/commands/source/apiserver/describe.go b/pkg/kn/commands/source/apiserver/describe.go
index ce502cd1..ad692bc1 100644
--- a/pkg/kn/commands/source/apiserver/describe.go
+++ b/pkg/kn/commands/source/apiserver/describe.go
@@ -42,9 +42,10 @@ func NewAPIServerDescribeCommand(p *commands.KnParams) *cobra.Command {
 	machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
 
 	command := &cobra.Command{
-		Use:     "describe NAME",
-		Short:   "Show details of an api-server source",
-		Example: describeExample,
+		Use:               "describe NAME",
+		Short:             "Show details of an api-server source",
+		Example:           describeExample,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn source apiserver describe' requires name of the source as single argument")
diff --git a/pkg/kn/commands/source/apiserver/update.go b/pkg/kn/commands/source/apiserver/update.go
index a10655b4..f369a8aa 100644
--- a/pkg/kn/commands/source/apiserver/update.go
+++ b/pkg/kn/commands/source/apiserver/update.go
@@ -38,6 +38,7 @@ func NewAPIServerUpdateCommand(p *commands.KnParams) *cobra.Command {
   # Update an ApiServerSource 'k8sevents' with different service account and sink service
   kn source apiserver update k8sevents --service-account newsa --sink ksvc:newsvc`,
 
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
 				return errors.New("requires the name of the source as single argument")
diff --git a/pkg/kn/commands/source/binding/delete.go b/pkg/kn/commands/source/binding/delete.go
index 608d3eac..ff1cf0dc 100644
--- a/pkg/kn/commands/source/binding/delete.go
+++ b/pkg/kn/commands/source/binding/delete.go
@@ -30,6 +30,7 @@ func NewBindingDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a sink binding with name 'my-binding'
   kn source binding delete my-binding`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("requires the name of the sink binding to delete as single argument")
diff --git a/pkg/kn/commands/source/binding/describe.go b/pkg/kn/commands/source/binding/describe.go
index ac9b98da..152e2c56 100644
--- a/pkg/kn/commands/source/binding/describe.go
+++ b/pkg/kn/commands/source/binding/describe.go
@@ -43,9 +43,10 @@ func NewBindingDescribeCommand(p *commands.KnParams) *cobra.Command {
 	machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
 
 	command := &cobra.Command{
-		Use:     "describe NAME",
-		Short:   "Show details of a sink binding",
-		Example: describeExample,
+		Use:               "describe NAME",
+		Short:             "Show details of a sink binding",
+		Example:           describeExample,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn source binding describe' requires name of the sink binding as single argument")
diff --git a/pkg/kn/commands/source/binding/update.go b/pkg/kn/commands/source/binding/update.go
index eb62c01e..3886e3f3 100644
--- a/pkg/kn/commands/source/binding/update.go
+++ b/pkg/kn/commands/source/binding/update.go
@@ -38,6 +38,7 @@ func NewBindingUpdateCommand(p *commands.KnParams) *cobra.Command {
   # Update the subject of a sink binding 'my-binding' to a new cronjob with label selector 'app=ping'  
   kn source binding update my-binding --subject cronjob:batch/v1beta1:app=ping"`,
 
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
 				return errors.New("requires the name of the sink binding to update as single argument")
diff --git a/pkg/kn/commands/source/container/delete.go b/pkg/kn/commands/source/container/delete.go
index 5374b452..03542339 100644
--- a/pkg/kn/commands/source/container/delete.go
+++ b/pkg/kn/commands/source/container/delete.go
@@ -33,6 +33,7 @@ func NewContainerDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a ContainerSource 'containersrc' in default namespace
   kn source container delete containersrc`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("requires the name of the source as single argument")
diff --git a/pkg/kn/commands/source/container/describe.go b/pkg/kn/commands/source/container/describe.go
index a4859c00..7e5b1efe 100644
--- a/pkg/kn/commands/source/container/describe.go
+++ b/pkg/kn/commands/source/container/describe.go
@@ -36,6 +36,7 @@ func NewContainerDescribeCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Describe a container source with name 'k8sevents'
   kn source container describe k8sevents`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn source container describe' requires name of the source as single argument")
diff --git a/pkg/kn/commands/source/ping/delete.go b/pkg/kn/commands/source/ping/delete.go
index a2557687..b7e8699b 100644
--- a/pkg/kn/commands/source/ping/delete.go
+++ b/pkg/kn/commands/source/ping/delete.go
@@ -30,6 +30,7 @@ func NewPingDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a Ping source 'my-ping'
   kn source ping delete my-ping`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'requires the name of the Ping source to delete as single argument")
diff --git a/pkg/kn/commands/source/ping/describe.go b/pkg/kn/commands/source/ping/describe.go
index 38bed794..b6fc2c11 100644
--- a/pkg/kn/commands/source/ping/describe.go
+++ b/pkg/kn/commands/source/ping/describe.go
@@ -41,9 +41,10 @@ func NewPingDescribeCommand(p *commands.KnParams) *cobra.Command {
 	machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
 
 	command := &cobra.Command{
-		Use:     "describe NAME",
-		Short:   "Show details of a ping source",
-		Example: describeExample,
+		Use:               "describe NAME",
+		Short:             "Show details of a ping source",
+		Example:           describeExample,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn source ping describe' requires name of the source as single argument")
diff --git a/pkg/kn/commands/source/ping/update.go b/pkg/kn/commands/source/ping/update.go
index 69344662..1eda2f25 100644
--- a/pkg/kn/commands/source/ping/update.go
+++ b/pkg/kn/commands/source/ping/update.go
@@ -42,6 +42,7 @@ func NewPingUpdateCommand(p *commands.KnParams) *cobra.Command {
   # Update the schedule of a Ping source 'my-ping' to fire every minute
   kn source ping update my-ping --schedule "* * * * *"`,
 
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
 				return errors.New("name of Ping source required")
diff --git a/pkg/kn/commands/subscription/delete.go b/pkg/kn/commands/subscription/delete.go
index 8d54dcec..26a7e235 100644
--- a/pkg/kn/commands/subscription/delete.go
+++ b/pkg/kn/commands/subscription/delete.go
@@ -32,6 +32,7 @@ func NewSubscriptionDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a subscription 'sub0'
   kn subscription delete sub0`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn subscription delete' requires the subscription name as single argument")
diff --git a/pkg/kn/commands/subscription/describe.go b/pkg/kn/commands/subscription/describe.go
index db70f733..aced44a8 100644
--- a/pkg/kn/commands/subscription/describe.go
+++ b/pkg/kn/commands/subscription/describe.go
@@ -43,6 +43,7 @@ func NewSubscriptionDescribeCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Describe a subscription 'pipe'
   kn subscription describe pipe`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn subscription describe' requires the subscription name given as single argument")
diff --git a/pkg/kn/commands/subscription/update.go b/pkg/kn/commands/subscription/update.go
index e8c63618..851755cc 100644
--- a/pkg/kn/commands/subscription/update.go
+++ b/pkg/kn/commands/subscription/update.go
@@ -43,7 +43,7 @@ func NewSubscriptionUpdateCommand(p *commands.KnParams) *cobra.Command {
 
   # Update a subscription 'sub1' with subscriber ksvc 'mirror', reply to a broker 'nest' and DeadLetterSink to a ksvc 'bucket'
   kn subscription update sub1 --sink mirror --sink-reply broker:nest --sink-dead-letter bucket`,
-
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
 				return errors.New("'kn subscription update' requires the subscription name given as single argument")
diff --git a/pkg/kn/commands/trigger/delete.go b/pkg/kn/commands/trigger/delete.go
index 24d70ad6..0335d2d2 100644
--- a/pkg/kn/commands/trigger/delete.go
+++ b/pkg/kn/commands/trigger/delete.go
@@ -30,6 +30,7 @@ func NewTriggerDeleteCommand(p *commands.KnParams) *cobra.Command {
 		Example: `
   # Delete a trigger 'mytrigger' in default namespace
   kn trigger delete mytrigger`,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'trigger delete' requires the name of the trigger as single argument")
diff --git a/pkg/kn/commands/trigger/describe.go b/pkg/kn/commands/trigger/describe.go
index 442143bb..5ca33a7a 100644
--- a/pkg/kn/commands/trigger/describe.go
+++ b/pkg/kn/commands/trigger/describe.go
@@ -40,9 +40,10 @@ func NewTriggerDescribeCommand(p *commands.KnParams) *cobra.Command {
 	machineReadablePrintFlags := genericclioptions.NewPrintFlags("")
 
 	command := &cobra.Command{
-		Use:     "describe NAME",
-		Short:   "Show details of a trigger",
-		Example: describeExample,
+		Use:               "describe NAME",
+		Short:             "Show details of a trigger",
+		Example:           describeExample,
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) != 1 {
 				return errors.New("'kn trigger describe' requires name of the trigger as single argument")
diff --git a/pkg/kn/commands/trigger/update.go b/pkg/kn/commands/trigger/update.go
index 1d686c79..d0ab1061 100644
--- a/pkg/kn/commands/trigger/update.go
+++ b/pkg/kn/commands/trigger/update.go
@@ -48,7 +48,7 @@ func NewTriggerUpdateCommand(p *commands.KnParams) *cobra.Command {
   # Update the sink of a trigger 'mytrigger' to 'ksvc:new-service'
   kn trigger update mytrigger --sink ksvc:new-service
   `,
-
+		ValidArgsFunction: commands.ResourceNameCompletionFunc(p),
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			if len(args) != 1 {
 				return errors.New("name of trigger required")
diff --git a/pkg/kn/commands/types.go b/pkg/kn/commands/types.go
index 73337107..f999cf11 100644
--- a/pkg/kn/commands/types.go
+++ b/pkg/kn/commands/types.go
@@ -94,6 +94,10 @@ func (params *KnParams) Initialize() {
 	if params.NewDynamicClient == nil {
 		params.NewDynamicClient = params.newDynamicClient
 	}
+
+	if params.NewSourcesV1beta2Client == nil {
+		params.NewSourcesV1beta2Client = params.newSourcesClientV1beta2
+	}
 }
 
 func (params *KnParams) newServingClient(namespace string) (clientservingv1.KnServingClient, error) {


Test patch:
diff --git a/pkg/kn/commands/completion_helper_test.go b/pkg/kn/commands/completion_helper_test.go
index 496c3a06..0422bd26 100644
--- a/pkg/kn/commands/completion_helper_test.go
+++ b/pkg/kn/commands/completion_helper_test.go
@@ -25,7 +25,16 @@ import (
 	"gotest.tools/v3/assert"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/tools/clientcmd"
+	v1beta1 "knative.dev/client/pkg/messaging/v1"
 	clientv1alpha1 "knative.dev/client/pkg/serving/v1alpha1"
+	clientsourcesv1 "knative.dev/client/pkg/sources/v1"
+	"knative.dev/client/pkg/sources/v1beta2"
+	v12 "knative.dev/eventing/pkg/apis/messaging/v1"
+	sourcesv1 "knative.dev/eventing/pkg/apis/sources/v1"
+	sourcesv1beta2 "knative.dev/eventing/pkg/apis/sources/v1beta2"
+	sourcesv1fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1/fake"
+	sourcesv1beta2fake "knative.dev/eventing/pkg/client/clientset/versioned/typed/sources/v1beta2/fake"
 
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
@@ -50,6 +59,19 @@ type testType struct {
 	resource   string
 }
 
+type mockMessagingClient struct {
+	channelsClient      v1beta1.KnChannelsClient
+	subscriptionsClient v1beta1.KnSubscriptionsClient
+}
+
+func (m *mockMessagingClient) ChannelsClient() v1beta1.KnChannelsClient {
+	return m.channelsClient
+}
+
+func (m *mockMessagingClient) SubscriptionsClient() v1beta1.KnSubscriptionsClient {
+	return m.subscriptionsClient
+}
+
 const (
 	testNs  = "test-ns"
 	errorNs = "error-ns"
@@ -185,9 +207,204 @@ var (
 	testNsDomains = []v1alpha1.DomainMapping{testDomain1, testDomain2, testDomain3}
 )
 
+var (
+	testTrigger1 = eventingv1.Trigger{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Trigger",
+			APIVersion: "eventing.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-1", Namespace: testNs},
+	}
+	testTrigger2 = eventingv1.Trigger{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Trigger",
+			APIVersion: "eventing.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-2", Namespace: testNs},
+	}
+	testTrigger3 = eventingv1.Trigger{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Trigger",
+			APIVersion: "eventing.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-trigger-3", Namespace: testNs},
+	}
+	testNsTriggers = []eventingv1.Trigger{testTrigger1, testTrigger2, testTrigger3}
+)
+
+var (
+	testContainerSource1 = sourcesv1.ContainerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ContainerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-1", Namespace: testNs},
+	}
+	testContainerSource2 = sourcesv1.ContainerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ContainerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-2", Namespace: testNs},
+	}
+	testContainerSource3 = sourcesv1.ContainerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ContainerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-container-source-3", Namespace: testNs},
+	}
+	testNsContainerSources = []sourcesv1.ContainerSource{testContainerSource1, testContainerSource2, testContainerSource3}
+	fakeSources            = &sourcesv1fake.FakeSourcesV1{Fake: &clienttesting.Fake{}}
+)
+
+var (
+	testApiServerSource1 = sourcesv1.ApiServerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ApiServerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-1", Namespace: testNs},
+	}
+	testApiServerSource2 = sourcesv1.ApiServerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ApiServerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-2", Namespace: testNs},
+	}
+	testApiServerSource3 = sourcesv1.ApiServerSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "ApiServerSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ApiServer-source-3", Namespace: testNs},
+	}
+	testNsApiServerSources = []sourcesv1.ApiServerSource{testApiServerSource1, testApiServerSource2, testApiServerSource3}
+)
+
+var (
+	testSinkBinding1 = sourcesv1.SinkBinding{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "SinkBinding",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-1", Namespace: testNs},
+	}
+	testSinkBinding2 = sourcesv1.SinkBinding{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "SinkBinding",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-2", Namespace: testNs},
+	}
+	testSinkBinding3 = sourcesv1.SinkBinding{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "SinkBinding",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-sink-binding-3", Namespace: testNs},
+	}
+	testNsSinkBindings = []sourcesv1.SinkBinding{testSinkBinding1, testSinkBinding2, testSinkBinding3}
+)
+
+var (
+	testPingSource1 = sourcesv1beta2.PingSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "PingSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-1", Namespace: testNs},
+	}
+	testPingSource2 = sourcesv1beta2.PingSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "PingSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-2", Namespace: testNs},
+	}
+	testPingSource3 = sourcesv1beta2.PingSource{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "PingSource",
+			APIVersion: "sources.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-ping-source-3", Namespace: testNs},
+	}
+	testNsPingSources  = []sourcesv1beta2.PingSource{testPingSource1, testPingSource2, testPingSource3}
+	fakeSourcesV1Beta2 = &sourcesv1beta2fake.FakeSourcesV1beta2{Fake: &clienttesting.Fake{}}
+)
+
+var (
+	testChannel1 = v12.Channel{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Channel",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-channel-1", Namespace: testNs},
+	}
+	testChannel2 = v12.Channel{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Channel",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-channel-2", Namespace: testNs},
+	}
+	testChannel3 = v12.Channel{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Channel",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-channel-3", Namespace: testNs},
+	}
+	testNsChannels = []v12.Channel{testChannel1, testChannel2, testChannel3}
+)
+
+var (
+	testSubscription1 = v12.Subscription{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Subscription",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-1", Namespace: testNs},
+	}
+	testSubscription2 = v12.Subscription{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Subscription",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-2", Namespace: testNs},
+	}
+	testSubscription3 = v12.Subscription{
+		TypeMeta: metav1.TypeMeta{
+			Kind:       "Subscription",
+			APIVersion: "messaging.knative.dev/v1",
+		},
+		ObjectMeta: metav1.ObjectMeta{Name: "test-subscription-3", Namespace: testNs},
+	}
+	testNsSubscriptions = []v12.Subscription{testSubscription1, testSubscription2, testSubscription3}
+)
+
 var knParams = initialiseKnParams()
 
 func initialiseKnParams() *KnParams {
+	blankConfig, err := clientcmd.NewClientConfigFromBytes([]byte(`kind: Config
+version: v1beta2
+users:
+- name: u
+clusters:
+- name: c
+  cluster:
+    server: example.com
+contexts:
+- name: x
+  context:
+    user: u
+    cluster: c
+current-context: x
+`))
+	if err != nil {
+		panic(err)
+	}
 	return &KnParams{
 		NewServingClient: func(namespace string) (v1.KnServingClient, error) {
 			return v1.NewKnServingClient(fakeServing, namespace), nil
@@ -201,6 +418,13 @@ func initialiseKnParams() *KnParams {
 		NewServingV1alpha1Client: func(namespace string) (clientv1alpha1.KnServingClient, error) {
 			return clientv1alpha1.NewKnServingClient(fakeServingAlpha, namespace), nil
 		},
+		NewSourcesClient: func(namespace string) (clientsourcesv1.KnSourcesClient, error) {
+			return clientsourcesv1.NewKnSourcesClient(fakeSources, namespace), nil
+		},
+		NewSourcesV1beta2Client: func(namespace string) (v1beta2.KnSourcesClient, error) {
+			return v1beta2.NewKnSourcesClient(fakeSourcesV1Beta2, namespace), nil
+		},
+		ClientConfig: blankConfig,
 	}
 }
 
@@ -296,7 +520,7 @@ func TestResourceNameCompletionFuncBroker(t *testing.T) {
 
 	fakeEventing.AddReactor("list", "brokers", func(action clienttesting.Action) (bool, runtime.Object, error) {
 		if action.GetNamespace() == errorNs {
-			return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
+			return true, nil, errors.NewInternalError(fmt.Errorf("unable to list brokers"))
 		}
 		return true, &eventingv1.BrokerList{Items: testNsBrokers}, nil
 	})
@@ -531,7 +755,7 @@ func TestResourceNameCompletionFuncRoute(t *testing.T) {
 	fakeServing.AddReactor("list", "routes",
 		func(a clienttesting.Action) (bool, runtime.Object, error) {
 			if a.GetNamespace() == errorNs {
-				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list routes"))
 			}
 			return true, &servingv1.RouteList{Items: testNsRoutes}, nil
 		})
@@ -609,7 +833,7 @@ func TestResourceNameCompletionFuncDomain(t *testing.T) {
 	fakeServingAlpha.AddReactor("list", "domainmappings",
 		func(a clienttesting.Action) (bool, runtime.Object, error) {
 			if a.GetNamespace() == errorNs {
-				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list services"))
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list domains"))
 			}
 			return true, &v1alpha1.DomainMappingList{Items: testNsDomains}, nil
 		})
@@ -681,6 +905,568 @@ func TestResourceNameCompletionFuncDomain(t *testing.T) {
 	}
 }
 
+func TestResourceNameCompletionFuncTrigger(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	fakeServing.AddReactor("list", "triggers",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			if a.GetNamespace() == errorNs {
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list triggers"))
+			}
+			return true, &eventingv1.TriggerList{Items: testNsTriggers}, nil
+		})
+
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"trigger",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"trigger",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"trigger",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"trigger",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"trigger",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+}
+
+func TestResourceNameCompletionFuncContainerSource(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	fakeSources.AddReactor("list", "containersources",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			if a.GetNamespace() == errorNs {
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list container sources"))
+			}
+			return true, &sourcesv1.ContainerSourceList{Items: testNsContainerSources}, nil
+		})
+
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"container",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"container",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"container",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"container",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"container",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+}
+
+func TestResourceNameCompletionFuncApiserverSource(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	fakeSources.AddReactor("list", "apiserversources",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			if a.GetNamespace() == errorNs {
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list apiserver sources"))
+			}
+			return true, &sourcesv1.ApiServerSourceList{Items: testNsApiServerSources}, nil
+		})
+
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"apiserver",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"apiserver",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"apiserver",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"apiserver",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"apiserver",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+}
+
+func TestResourceNameCompletionFuncBindingSource(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	fakeSources.AddReactor("list", "sinkbindings",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			if a.GetNamespace() == errorNs {
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list binding sources"))
+			}
+			return true, &sourcesv1.SinkBindingList{Items: testNsSinkBindings}, nil
+		})
+
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"binding",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"binding",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"binding",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"binding",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"binding",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+}
+
+func TestResourceNameCompletionFuncPingSource(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	fakeSourcesV1Beta2.AddReactor("list", "pingsources",
+		func(a clienttesting.Action) (bool, runtime.Object, error) {
+			if a.GetNamespace() == errorNs {
+				return true, nil, errors.NewInternalError(fmt.Errorf("unable to list ping sources"))
+			}
+			return true, &sourcesv1beta2.PingSourceList{Items: testNsPingSources}, nil
+		})
+
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"ping",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"ping",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"ping",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"ping",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"ping",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+}
+
+func TestResourceNameCompletionFuncChannel(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	channelClient := v1beta1.NewMockKnChannelsClient(t)
+	channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
+	channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
+
+	channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
+	channelClient.Recorder().ListChannel(&v12.ChannelList{Items: testNsChannels}, nil)
+
+	channelClient.Recorder().ListChannel(&v12.ChannelList{}, fmt.Errorf("error listing channels"))
+	channelClient.Recorder().ListChannel(&v12.ChannelList{}, fmt.Errorf("error listing channels"))
+
+	messagingClient := &mockMessagingClient{channelClient, nil}
+
+	knParams.NewMessagingClient = func(namespace string) (v1beta1.KnMessagingClient, error) {
+		return messagingClient, nil
+	}
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"channel",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"channel",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"channel",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"channel",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"channel",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+	channelClient.Recorder().Validate()
+}
+
+func TestResourceNameCompletionFuncSubscription(t *testing.T) {
+	completionFunc := ResourceNameCompletionFunc(knParams)
+
+	subscriptionsClient := v1beta1.NewMockKnSubscriptionsClient(t)
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
+
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{Items: testNsSubscriptions}, nil)
+
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{}, fmt.Errorf("error listing channels"))
+	subscriptionsClient.Recorder().ListSubscription(&v12.SubscriptionList{}, fmt.Errorf("error listing channels"))
+
+	messagingClient := &mockMessagingClient{nil, subscriptionsClient}
+
+	knParams.NewMessagingClient = func(namespace string) (v1beta1.KnMessagingClient, error) {
+		return messagingClient, nil
+	}
+	tests := []testType{
+		{
+			"Empty suggestions when non-zero args",
+			testNs,
+			knParams,
+			[]string{"xyz"},
+			"",
+			"subscription",
+		},
+		{
+			"Empty suggestions when no namespace flag",
+			"",
+			knParams,
+			nil,
+			"",
+			"subscription",
+		},
+		{
+			"Suggestions when test-ns namespace set",
+			testNs,
+			knParams,
+			nil,
+			"",
+			"subscription",
+		},
+		{
+			"Empty suggestions when toComplete is not a prefix",
+			testNs,
+			knParams,
+			nil,
+			"xyz",
+			"subscription",
+		},
+		{
+			"Empty suggestions when error during list operation",
+			errorNs,
+			knParams,
+			nil,
+			"",
+			"subscription",
+		},
+	}
+	for _, tt := range tests {
+		cmd := getResourceCommandWithTestSubcommand(tt.resource, tt.namespace != "", tt.resource != "no-parent")
+		t.Run(tt.name, func(t *testing.T) {
+			config := &completionConfig{
+				params:     tt.p,
+				command:    cmd,
+				args:       tt.args,
+				toComplete: tt.toComplete,
+			}
+			expectedFunc := resourceToFuncMap[tt.resource]
+			if expectedFunc == nil {
+				expectedFunc = func(config *completionConfig) []string {
+					return []string{}
+				}
+			}
+			cmd.Flags().Set("namespace", tt.namespace)
+			actualSuggestions, actualDirective := completionFunc(cmd, tt.args, tt.toComplete)
+			expectedSuggestions := expectedFunc(config)
+			expectedDirective := cobra.ShellCompDirectiveNoFileComp
+			assert.DeepEqual(t, actualSuggestions, expectedSuggestions)
+			assert.Equal(t, actualDirective, expectedDirective)
+		})
+	}
+	subscriptionsClient.Recorder().Validate()
+}
+
 func getResourceCommandWithTestSubcommand(resource string, addNamespace, addSubcommand bool) *cobra.Command {
 	testCommand := &cobra.Command{
 		Use: resource,