diff --git a/pkg/extension/action_url_test.go b/pkg/extension/action_url_test.go
index be9a924b9478c316dc80d8deb629e044664df237..e4545b39a21922222ff7a0f140f8488007168f22 100644
--- a/pkg/extension/action_url_test.go
+++ b/pkg/extension/action_url_test.go
@@ -133,3 +133,40 @@ func TestActionURL_String(t *testing.T) {
 		})
 	}
 }
+
+func TestActionURL_Action(t *testing.T) {
+	tests := []struct {
+		name string
+		url  string
+		want string
+	}{
+		{
+			name: "GRPC action",
+			url:  "grpc:///perxisweb/build-site",
+			want: "build-site",
+		},
+		{
+			name: "UI action #1",
+			url:  "ui:///space/env/coll",
+		},
+		{
+			name: "UI action deprecated call #2",
+			url:  "space/env/coll",
+		},
+		{
+			name: "Https action",
+			url:  "https://perx.ru",
+		},
+		{
+			name: "With action deprecated call",
+			url:  "extension-id",
+			want: "extension-id",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			p, _ := NewActionURL(tt.url)
+			assert.Equalf(t, tt.want, p.Action(), "Action()")
+		})
+	}
+}
diff --git a/pkg/extension/service/extension_test.go b/pkg/extension/service/extension_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..929f85f3cfa457928ffbcbf368efe1702593f00b
--- /dev/null
+++ b/pkg/extension/service/extension_test.go
@@ -0,0 +1,71 @@
+package service
+
+import (
+	"context"
+	"reflect"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/extension"
+)
+
+func TestExtension_Action(t *testing.T) {
+	tests := []struct {
+		name    string
+		desc    *extension.ExtensionDescriptor
+		in      *extension.ActionRequest
+		router  extension.ActionRouter
+		want    *extension.ActionResponse
+		wantErr bool
+	}{
+		{
+			name: "OK",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test/action", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			want: &extension.ActionResponse{State: extension.ResponseDone},
+			router: extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone}, nil
+			}),
+			wantErr: false,
+		},
+		{
+			name: "OK (chain)",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test/action2", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			want: &extension.ActionResponse{State: extension.ResponseParametersRequired},
+			router: extension.Chain(extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone}, nil
+			}), extension.Named("action2", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseParametersRequired}, nil
+			})),
+			wantErr: false,
+		},
+		{
+			name: "Error (no extension)",
+			desc: &extension.ExtensionDescriptor{Extension: "test", Title: "Test Extension", Description: "desc", Version: "v.0.0.1"},
+			in:   &extension.ActionRequest{Action: "grpc:///test2/action2", SpaceId: "sp", EnvId: "env", CollectionId: "coll"},
+			want: nil,
+			router: extension.Chain(extension.Named("action", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseDone}, nil
+			}), extension.Named("action2", func(ctx context.Context, url *extension.ActionURL, req *extension.ActionRequest) (*extension.ActionResponse, error) {
+				return &extension.ActionResponse{State: extension.ResponseParametersRequired}, nil
+			})),
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := &Extension{
+				desc:   tt.desc,
+				router: tt.router,
+			}
+			got, err := s.Action(context.Background(), tt.in)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("Action() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("Action() got = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}