Repository: kubesphere/ks-devops
Pull request #434 (2022-01-05 14:14:39)

Add unit tests for the APIs
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubesphere/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubesphere/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding conventions followed by the KubeSphere community: https://github.com/kubesphere/community/blob/master/developer-guide/development/coding-conventions.md
4. Additional open-source best practice: https://github.com/LinuxSuRen/open-source-best-practice
-->

### What type of PR is this?
<!-- 
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design
/kind chore

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->
/kind chore

### What this PR does / why we need it:

### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
Please leave it or change # to be None if there is no corresponding issue that exists
-->
Fixes #

### Special notes for reviewers:
<!--
You can use the following command to let the DevOps SIG members help you to review your PR.
/cc @kubesphere/sig-devops 
And please avoid cc any individual.
-->
```
None
```

### Does this PR introduce a user-facing change??
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended-release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

Please keep the note be same as your PR title if you believe it should be in the release notes.
-->
```release-note
None
```


ks-ci-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubesphere/ks-devops/pull/434#" title="Author self-approved">LinuxSuRen</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubesphere%2Fks-devops).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubesphere/ks-devops/blob/master/OWNERS)~~ [LinuxSuRen]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
codecov[bot]: # [Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) Report
> Merging [#434](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (4a7eb7e) into [master](https://codecov.io/gh/kubesphere/ks-devops/commit/d3b19c56b6175c95293fb6c93ace8f053cb62046?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) (d3b19c5) will **increase** coverage by `1.29%`.
> The diff coverage is `25.00%`.

[![Impacted file tree graph](https://codecov.io/gh/kubesphere/ks-devops/pull/434/graphs/tree.svg?width=650&height=150&src=pr&token=qSK4RBbyNg&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)

```diff
@@            Coverage Diff            @@
##           master    #434      +/-   ##
=========================================
+ Coverage    7.94%   9.24%   +1.29%     
=========================================
  Files         102     104       +2     
  Lines       21815   22197     +382     
=========================================
+ Hits         1734    2052     +318     
- Misses      19957   20012      +55     
- Partials      124     133       +9     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `9.24% <25.00%> (+1.29%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | Coverage Δ | |
|---|---|---|
| [pkg/client/k8s/fake\_client.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2NsaWVudC9rOHMvZmFrZV9jbGllbnQuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9oYW5kbGVyLmdv) | `44.57% <100.00%> (ø)` | |
| [controllers/pipeline/pipeline\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvcGlwZWxpbmUvcGlwZWxpbmVfY29udHJvbGxlci5nbw==) | `38.23% <0.00%> (-3.68%)` | :arrow_down: |
| [controllers/s2irun/s2irun\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvczJpcnVuL3MyaXJ1bl9jb250cm9sbGVyLmdv) | `33.57% <0.00%> (-3.65%)` | :arrow_down: |
| [...rs/devopscredential/devopscredential\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvZGV2b3BzY3JlZGVudGlhbC9kZXZvcHNjcmVkZW50aWFsX2NvbnRyb2xsZXIuZ28=) | `33.10% <0.00%> (-3.38%)` | :arrow_down: |
| [...trollers/devopsproject/devopsproject\_controller.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-Y29udHJvbGxlcnMvZGV2b3BzcHJvamVjdC9kZXZvcHNwcm9qZWN0X2NvbnRyb2xsZXIuZ28=) | `40.62% <0.00%> (-2.61%)` | :arrow_down: |
| [pkg/kapis/devops/v1alpha3/register.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9yZWdpc3Rlci5nbw==) | `100.00% <0.00%> (ø)` | |
| [pkg/kapis/devops/v1alpha3/pipelinerun/handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZXJ1bi9oYW5kbGVyLmdv) | `32.65% <0.00%> (+32.65%)` | :arrow_up: |
| [pkg/kapis/devops/v1alpha3/pipeline/handler.go](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere#diff-cGtnL2thcGlzL2Rldm9wcy92MWFscGhhMy9waXBlbGluZS9oYW5kbGVyLmdv) | `40.00% <0.00%> (+40.00%)` | :arrow_up: |
| ... and [2 more](https://codecov.io/gh/kubesphere/ks-devops/pull/434/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Last update [d3b19c5...4a7eb7e](https://codecov.io/gh/kubesphere/ks-devops/pull/434?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kubesphere).

LinuxSuRen: /cc @kubesphere/sig-devops 
ks-ci-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubesphere/ks-devops/pull/434#pullrequestreview-846325831" title="LGTM">JohnNiang</a>*, *<a href="https://github.com/kubesphere/ks-devops/pull/434#" title="Author self-approved">LinuxSuRen</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubesphere%2Fks-devops).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubesphere/ks-devops/blob/master/OWNERS)~~ [LinuxSuRen]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
JohnNiang: /label tide/merge-method-squash

Patch:
diff --git a/.golangci.yml b/.golangci.yml
index 0a4166a..4da87a2 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -143,6 +143,7 @@ run:
 issues:
   exclude:
     - EXC0012
+    - SA1019
   exclude-use-default: false
 linters:
   enable:
diff --git a/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go b/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
index b364f8e..98ee401 100644
--- a/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/api/devops/v1alpha1/zz_generated.deepcopy.go
@@ -1,3 +1,4 @@
+//go:build !ignore_autogenerated
 // +build !ignore_autogenerated
 
 /*
diff --git a/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go b/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
index 27798d0..d8ff289 100644
--- a/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
+++ b/pkg/api/devops/v1alpha3/zz_generated.deepcopy.go
@@ -1,3 +1,4 @@
+//go:build !ignore_autogenerated
 // +build !ignore_autogenerated
 
 /*
diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go
index 029eb11..1596749 100644
--- a/pkg/apiserver/apiserver.go
+++ b/pkg/apiserver/apiserver.go
@@ -147,11 +147,11 @@ func (s *APIServer) installKubeSphereAPIs() {
 		s.KubernetesClient,
 		jenkinsCore))
 	devopsv1alpha3.AddToContainer(s.container, s.DevopsClient, s.KubernetesClient, s.Client)
-	utilruntime.Must(oauth.AddToContainer(s.container,
+	oauth.AddToContainer(s.container,
 		auth.NewTokenOperator(
 			s.CacheClient,
 			s.Config.AuthenticationOptions),
-	))
+	)
 }
 
 func (s *APIServer) Run(stopCh <-chan struct{}) (err error) {
diff --git a/pkg/client/k8s/fake_client.go b/pkg/client/k8s/fake_client.go
index f8e1153..404ec43 100644
--- a/pkg/client/k8s/fake_client.go
+++ b/pkg/client/k8s/fake_client.go
@@ -34,21 +34,24 @@ type FakeClient struct {
 	ApiExtensionClient apiextensionsclient.Interface
 	MasterURL          string
 	KubeConfig         *rest.Config
+
+	ksClient versioned.Interface
 }
 
 func (n *FakeClient) KubeSphere() versioned.Interface {
-	panic("implement me")
+	return n.ksClient
 }
 
 func NewFakeClientSets(k8sClient kubernetes.Interface, discoveryClient *discovery.DiscoveryClient,
 	apiextensionsclient apiextensionsclient.Interface,
-	masterURL string, kubeConfig *rest.Config) Client {
+	masterURL string, kubeConfig *rest.Config, ksClient versioned.Interface) Client {
 	return &FakeClient{
 		K8sClient:          k8sClient,
 		DiscoveryClient:    discoveryClient,
 		ApiExtensionClient: apiextensionsclient,
 		MasterURL:          masterURL,
 		KubeConfig:         kubeConfig,
+		ksClient:           ksClient,
 	}
 }
 
diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go
index e83630d..620325e 100644
--- a/pkg/constants/constants.go
+++ b/pkg/constants/constants.go
@@ -129,9 +129,14 @@ const (
 	NotificationTag             = "Notification"
 	NotificationSecretNamespace = "kubesphere-monitoring-federated"
 	NotificationManagedLabel    = "notification.kubesphere.io/managed"
-	K8SToken                    = "k8s.token"
 )
 
+// K8SToken is the context key of k8s token
+var K8SToken = ContextKeyK8SToken("k8s.token")
+
+// ContextKeyK8SToken represents a type alias for the context key
+type ContextKeyK8SToken string
+
 const (
 	StatusActive     = "active"
 	StatusDeleted    = "deleted"
diff --git a/pkg/kapis/devops/v1alpha3/handler.go b/pkg/kapis/devops/v1alpha3/handler.go
index 536ca22..8dc8a4f 100644
--- a/pkg/kapis/devops/v1alpha3/handler.go
+++ b/pkg/kapis/devops/v1alpha3/handler.go
@@ -321,7 +321,7 @@ func (h *devopsHandler) ListCredential(request *restful.Request, response *restf
 	devops := request.PathParameter("devops")
 	query := query.ParseQueryParameter(request)
 
-	if client, err := h.getDevOps(request); err == nil {
+	if client, err := h.getDevOps(request); err == nil && client != nil {
 		objs, err := client.ListCredentialObj(devops, query)
 		if err != nil {
 			klog.Error(err)
@@ -417,14 +417,14 @@ func (h *devopsHandler) DeleteCredential(request *restful.Request, response *res
 
 func (h *devopsHandler) getDevOps(request *restful.Request) (operator devops.DevopsOperator, err error) {
 	ctx := request.Request.Context()
-	token := ctx.Value(constants.K8SToken).(string)
+	token := ctx.Value(constants.K8SToken).(constants.ContextKeyK8SToken)
 
 	var k8sClient k8s.Client
 	if token == "" {
 		k8sClient = h.k8sClient
 	} else {
 		klog.V(9).Infof("get DevOps client with token: %s", token)
-		k8sClient, err = k8s.NewKubernetesClientWithToken(token, h.k8sClient.Config().Host)
+		k8sClient, err = k8s.NewKubernetesClientWithToken(string(token), h.k8sClient.Config().Host)
 	}
 
 	if err == nil {
diff --git a/pkg/kapis/oauth/handler.go b/pkg/kapis/oauth/handler.go
index 243ed0b..d12c843 100644
--- a/pkg/kapis/oauth/handler.go
+++ b/pkg/kapis/oauth/handler.go
@@ -65,7 +65,7 @@ func newHandler(tokenOperator auth.TokenManagementInterface) *handler {
 	return &handler{tokenOperator: tokenOperator}
 }
 
-// Implement webhook authentication interface
+// TokenReview Implement webhook authentication interface
 // https://kubernetes.io/docs/reference/access-authn-authz/authentication/#webhook-token-authentication
 func (h *handler) TokenReview(req *restful.Request, resp *restful.Response) {
 	var tokenReview TokenReview
diff --git a/pkg/kapis/oauth/register.go b/pkg/kapis/oauth/register.go
index b797964..c94acff 100644
--- a/pkg/kapis/oauth/register.go
+++ b/pkg/kapis/oauth/register.go
@@ -27,7 +27,7 @@ import (
 	"kubesphere.io/devops/pkg/models/auth"
 )
 
-func AddToContainer(c *restful.Container, tokenOperator auth.TokenManagementInterface) error {
+func AddToContainer(c *restful.Container, tokenOperator auth.TokenManagementInterface) {
 	ws := &restful.WebService{}
 	ws.Path("/oauth").
 		Consumes(restful.MIME_JSON).
@@ -45,5 +45,4 @@ func AddToContainer(c *restful.Container, tokenOperator auth.TokenManagementInte
 		Returns(http.StatusOK, api.StatusOK, TokenReview{}).
 		Metadata(restfulspec.KeyOpenAPITags, []string{constants.AuthenticationTag}))
 	c.Add(ws)
-	return nil
 }


Test patch:
diff --git a/pkg/kapis/devops/v1alpha2/register_test.go b/pkg/kapis/devops/v1alpha2/register_test.go
new file mode 100644
index 0000000..fb8a4cc
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha2/register_test.go
@@ -0,0 +1,206 @@
+package v1alpha2
+
+import (
+	"context"
+	"github.com/emicklei/go-restful"
+	"github.com/jenkins-zh/jenkins-client/pkg/core"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	fakeclientset "kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	fakedevops "kubesphere.io/devops/pkg/client/devops/fake"
+	"kubesphere.io/devops/pkg/client/k8s"
+	"kubesphere.io/devops/pkg/constants"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestAPIsExist(t *testing.T) {
+	httpWriter := httptest.NewRecorder()
+
+	err := AddToContainer(restful.DefaultContainer, nil, fakedevops.NewFakeDevops(nil),
+		nil,
+		fakeclientset.NewSimpleClientset(&v1alpha3.DevOpsProject{
+			ObjectMeta: metav1.ObjectMeta{Name: "fake"},
+		}), nil, "", k8s.NewFakeClientSets(k8sfake.NewSimpleClientset(), nil, nil, "", nil,
+			fakeclientset.NewSimpleClientset(&v1alpha3.DevOpsProject{
+				ObjectMeta: metav1.ObjectMeta{Name: "fake"},
+			})), core.JenkinsCore{})
+	assert.Nil(t, err)
+
+	type args struct {
+		method string
+		uri    string
+	}
+	tests := []struct {
+		name string
+		args args
+	}{{
+		name: "credential usage",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/credentials/fake/usage",
+		},
+	}, {
+		name: "get a pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake",
+		},
+	}, {
+		name: "search API",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/search",
+		},
+	}, {
+		name: "get a pipeline run",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/runs/fake",
+		},
+	}, {
+		name: "get a pipeline list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/runs",
+		},
+	}, {
+		name: "stop a pipeline run",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines/fake/runs/fake/stop",
+		},
+	}, {
+		name: "replay a pipeline run",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines/fake/runs/fake/replay",
+		},
+	}, {
+		name: "start a pipeline run",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines/fake/runs",
+		},
+	}, {
+		name: "get artifacts from a pipeline run",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/runs/fake/artifacts",
+		},
+	}, {
+		name: "get log output from a pipeline run",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/runs/fake/log",
+		},
+	}, {
+		name: "get log output from a pipeline run step",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/runs/fake/nodes/fake/steps/fake/log",
+		},
+	}, {
+		name: "get branches from a pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/branches",
+		},
+	}, {
+		name: "scan a pipeline",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines/fake/scan",
+		},
+	}, {
+		name: "get consolelog from a pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake/consolelog",
+		},
+	}, {
+		name: "get crumb issuer",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/crumbissuer",
+		},
+	}, {
+		name: "get servers",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/scms/fake/servers",
+		},
+	}, {
+		name: "get organizations",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/scms/fake/organizations",
+		},
+	}, {
+		name: "get repositories",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/scms/fake/organizations/fake/repositories",
+		},
+	}, {
+		name: "scm verify",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/scms/fake/verify",
+		},
+	}, {
+		name: "webhook-git",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/webhook/git",
+		},
+	}, {
+		name: "webhook-git",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/webhook/git",
+		},
+	}, {
+		name: "webhook-github",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/webhook/github",
+		},
+	}, {
+		name: "generic-trigger",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/webhook/generic-trigger",
+		},
+	}, {
+		name: "checkScriptCompile",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines/fake/checkScriptCompile",
+		},
+	}, {
+		name: "checkCron",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/checkCron",
+		},
+	}, {
+		name: "tojenkinsfile",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/tojenkinsfile",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha2"+tt.args.uri, nil)
+			httpRequest = httpRequest.WithContext(context.WithValue(context.TODO(), constants.K8SToken, constants.ContextKeyK8SToken("")))
+			restful.DefaultContainer.Dispatch(httpWriter, httpRequest)
+			assert.NotEqual(t, httpWriter.Code, 404)
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/pipeline/register_test.go b/pkg/kapis/devops/v1alpha3/pipeline/register_test.go
new file mode 100644
index 0000000..3a1b374
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/pipeline/register_test.go
@@ -0,0 +1,52 @@
+package pipeline
+
+import (
+	"github.com/emicklei/go-restful"
+	"github.com/stretchr/testify/assert"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/apiserver/runtime"
+	"net/http"
+	"net/http/httptest"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"testing"
+)
+
+func TestAPIsExist(t *testing.T) {
+	httpWriter := httptest.NewRecorder()
+	wsWithGroup := runtime.NewWebService(v1alpha1.GroupVersion)
+
+	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	assert.Nil(t, err)
+
+	RegisterRoutes(wsWithGroup, fake.NewFakeClientWithScheme(schema))
+	restful.DefaultContainer.Add(wsWithGroup)
+
+	type args struct {
+		method string
+		uri    string
+	}
+	tests := []struct {
+		name string
+		args args
+	}{{
+		name: "get branches from the pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/fake/pipelines/fake/branches",
+		},
+	}, {
+		name: "get a branch from the pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/fake/pipelines/fake/branches/fake",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha1"+tt.args.uri, nil)
+			restful.DefaultContainer.Dispatch(httpWriter, httpRequest)
+			assert.NotEqual(t, httpWriter.Code, 404)
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go b/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
new file mode 100644
index 0000000..ee62871
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/pipelinerun/register_test.go
@@ -0,0 +1,64 @@
+package pipelinerun
+
+import (
+	"github.com/emicklei/go-restful"
+	"github.com/stretchr/testify/assert"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/apiserver/runtime"
+	"net/http"
+	"net/http/httptest"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"testing"
+)
+
+func TestAPIsExist(t *testing.T) {
+	httpWriter := httptest.NewRecorder()
+	wsWithGroup := runtime.NewWebService(v1alpha1.GroupVersion)
+
+	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	assert.Nil(t, err)
+
+	RegisterRoutes(wsWithGroup, fake.NewFakeClientWithScheme(schema))
+	restful.DefaultContainer.Add(wsWithGroup)
+
+	type args struct {
+		method string
+		uri    string
+	}
+	tests := []struct {
+		name string
+		args args
+	}{{
+		name: "pipelinerun list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/fake/pipelines/fake/pipelineruns",
+		},
+	}, {
+		name: "create a pipelinerun",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/namespaces/fake/pipelines/fake/pipelineruns",
+		},
+	}, {
+		name: "get a pipelinerun",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/fake/pipelineruns/fake",
+		},
+	}, {
+		name: "get node details",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/namespaces/fake/pipelineruns/fake/nodedetails",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha1"+tt.args.uri, nil)
+			restful.DefaultContainer.Dispatch(httpWriter, httpRequest)
+			assert.NotEqual(t, httpWriter.Code, 404)
+		})
+	}
+}
diff --git a/pkg/kapis/devops/v1alpha3/register_test.go b/pkg/kapis/devops/v1alpha3/register_test.go
new file mode 100644
index 0000000..32d819a
--- /dev/null
+++ b/pkg/kapis/devops/v1alpha3/register_test.go
@@ -0,0 +1,140 @@
+package v1alpha3
+
+import (
+	"context"
+	"github.com/emicklei/go-restful"
+	"github.com/stretchr/testify/assert"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	k8sfake "k8s.io/client-go/kubernetes/fake"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha1"
+	"kubesphere.io/devops/pkg/api/devops/v1alpha3"
+	fakeclientset "kubesphere.io/devops/pkg/client/clientset/versioned/fake"
+	fakedevops "kubesphere.io/devops/pkg/client/devops/fake"
+	"kubesphere.io/devops/pkg/client/k8s"
+	"kubesphere.io/devops/pkg/constants"
+	"net/http"
+	"net/http/httptest"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"testing"
+)
+
+func TestAPIsExist(t *testing.T) {
+	httpWriter := httptest.NewRecorder()
+	schema, err := v1alpha1.SchemeBuilder.Register().Build()
+	assert.Nil(t, err)
+
+	AddToContainer(restful.DefaultContainer, fakedevops.NewFakeDevops(nil),
+		k8s.NewFakeClientSets(k8sfake.NewSimpleClientset(), nil, nil, "", nil,
+			fakeclientset.NewSimpleClientset(&v1alpha3.DevOpsProject{
+				ObjectMeta: metav1.ObjectMeta{Name: "fake"},
+			})),
+		fake.NewFakeClientWithScheme(schema))
+
+	type args struct {
+		method string
+		uri    string
+	}
+	tests := []struct {
+		name string
+		args args
+	}{{
+		name: "credential list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/credentials",
+		},
+	}, {
+		name: "create a credential",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/credentials",
+		},
+	}, {
+		name: "get a credential",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/credentials/fake",
+		},
+	}, {
+		name: "update a credential",
+		args: args{
+			method: http.MethodPut,
+			uri:    "/devops/fake/credentials/fake",
+		},
+	}, {
+		name: "delete a credential",
+		args: args{
+			method: http.MethodDelete,
+			uri:    "/devops/fake/credentials/fake",
+		},
+	}, {
+		name: "get pipeline list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines",
+		},
+	}, {
+		name: "create a pipeline",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/devops/fake/pipelines",
+		},
+	}, {
+		name: "get a pipeline",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/devops/fake/pipelines/fake",
+		},
+	}, {
+		name: "update a pipeline",
+		args: args{
+			method: http.MethodPut,
+			uri:    "/devops/fake/pipelines/fake",
+		},
+	}, {
+		name: "delete a pipeline",
+		args: args{
+			method: http.MethodDelete,
+			uri:    "/devops/fake/pipelines/fake",
+		},
+	}, {
+		name: "get devops list",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/workspaces/fake/devops",
+		},
+	}, {
+		name: "create a devops",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/workspaces/fake/devops",
+		},
+	}, {
+		name: "get a devops",
+		args: args{
+			method: http.MethodGet,
+			uri:    "/workspaces/fake/devops/fake",
+		},
+	}, {
+		name: "update a devops",
+		args: args{
+			method: http.MethodPut,
+			uri:    "/workspaces/fake/devops/fake",
+		},
+	}, {
+		name: "delete a devops",
+		args: args{
+			method: http.MethodDelete,
+			uri:    "/workspaces/fake/devops/fake",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/kapis/devops.kubesphere.io/v1alpha3"+tt.args.uri, nil)
+			httpRequest = httpRequest.WithContext(context.WithValue(context.TODO(), constants.K8SToken, constants.ContextKeyK8SToken("")))
+			restful.DefaultContainer.Dispatch(httpWriter, httpRequest)
+			assert.NotEqual(t, httpWriter.Code, 404)
+		})
+	}
+}
diff --git a/pkg/kapis/oauth/register_test.go b/pkg/kapis/oauth/register_test.go
new file mode 100644
index 0000000..d6c8595
--- /dev/null
+++ b/pkg/kapis/oauth/register_test.go
@@ -0,0 +1,40 @@
+package oauth
+
+import (
+	"bytes"
+	"github.com/emicklei/go-restful"
+	"github.com/stretchr/testify/assert"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+)
+
+func TestAPIsExist(t *testing.T) {
+	httpWriter := httptest.NewRecorder()
+
+	AddToContainer(restful.DefaultContainer, nil)
+
+	type args struct {
+		method string
+		uri    string
+	}
+	tests := []struct {
+		name string
+		args args
+	}{{
+		name: "authenticate",
+		args: args{
+			method: http.MethodPost,
+			uri:    "/authenticate",
+		},
+	}}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			httpRequest, _ := http.NewRequest(tt.args.method,
+				"http://fake.com/oauth"+tt.args.uri, bytes.NewBuffer([]byte("{}")))
+			httpRequest.Header.Set("Content-Type", "application/json")
+			restful.DefaultContainer.Dispatch(httpWriter, httpRequest)
+			assert.NotEqual(t, httpWriter.Code, 404)
+		})
+	}
+}