mirror of https://github.com/hashicorp/consul
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
620 lines
21 KiB
620 lines
21 KiB
// Copyright (c) HashiCorp, Inc. |
|
// SPDX-License-Identifier: BUSL-1.1 |
|
|
|
package http |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"net/http/httptest" |
|
"strings" |
|
"testing" |
|
|
|
"github.com/stretchr/testify/mock" |
|
"github.com/stretchr/testify/require" |
|
|
|
"github.com/hashicorp/go-hclog" |
|
|
|
svc "github.com/hashicorp/consul/agent/grpc-external/services/resource" |
|
svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" |
|
"github.com/hashicorp/consul/internal/resource" |
|
"github.com/hashicorp/consul/internal/resource/demo" |
|
"github.com/hashicorp/consul/proto-public/pbresource" |
|
pbdemov1 "github.com/hashicorp/consul/proto/private/pbdemo/v1" |
|
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2" |
|
"github.com/hashicorp/consul/sdk/testutil" |
|
) |
|
|
|
const testACLTokenArtistReadPolicy = "00000000-0000-0000-0000-000000000001" |
|
const testACLTokenArtistWritePolicy = "00000000-0000-0000-0000-000000000002" |
|
const testACLTokenArtistListPolicy = "00000000-0000-0000-0000-000000000003" |
|
const fakeToken = "fake-token" |
|
|
|
func parseToken(req *http.Request, token *string) { |
|
*token = req.Header.Get("x-consul-token") |
|
} |
|
|
|
func TestResourceHandler_InputValidation(t *testing.T) { |
|
type testCase struct { |
|
description string |
|
request *http.Request |
|
response *httptest.ResponseRecorder |
|
expectedResponseCode int |
|
responseBodyContains string |
|
} |
|
|
|
client := svctest.NewResourceServiceBuilder(). |
|
WithRegisterFns(demo.RegisterTypes). |
|
Run(t) |
|
|
|
resourceHandler := resourceHandler{ |
|
resource.Registration{ |
|
Type: demo.TypeV2Artist, |
|
Proto: &pbdemov2.Artist{}, |
|
Scope: resource.ScopeNamespace, |
|
}, |
|
client, |
|
func(req *http.Request, token *string) { return }, |
|
hclog.NewNullLogger(), |
|
} |
|
|
|
testCases := []testCase{ |
|
{ |
|
description: "missing resource name", |
|
request: httptest.NewRequest("PUT", "/?partition=default&peer_name=local&namespace=default", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)), |
|
response: httptest.NewRecorder(), |
|
expectedResponseCode: http.StatusBadRequest, |
|
responseBodyContains: "resource.id.name invalid", |
|
}, |
|
{ |
|
description: "wrong schema", |
|
request: httptest.NewRequest("PUT", "/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"dada": { |
|
"name": "Keith Urban", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)), |
|
response: httptest.NewRecorder(), |
|
expectedResponseCode: http.StatusBadRequest, |
|
responseBodyContains: "Request body didn't follow the resource schema", |
|
}, |
|
{ |
|
description: "invalid request body", |
|
request: httptest.NewRequest("PUT", "/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader("bad-input")), |
|
response: httptest.NewRecorder(), |
|
expectedResponseCode: http.StatusBadRequest, |
|
responseBodyContains: "Request body format is invalid", |
|
}, |
|
{ |
|
description: "no id", |
|
request: httptest.NewRequest("DELETE", "/?partition=default&peer_name=local&namespace=default", strings.NewReader("")), |
|
response: httptest.NewRecorder(), |
|
expectedResponseCode: http.StatusBadRequest, |
|
responseBodyContains: "id.name invalid", |
|
}, |
|
} |
|
|
|
for _, tc := range testCases { |
|
t.Run(tc.description, func(t *testing.T) { |
|
resourceHandler.ServeHTTP(tc.response, tc.request) |
|
|
|
response := tc.response.Result() |
|
|
|
defer response.Body.Close() |
|
|
|
b, err := io.ReadAll(tc.response.Body) |
|
require.NoError(t, err) |
|
|
|
require.Equal(t, tc.expectedResponseCode, tc.response.Result().StatusCode) |
|
require.Contains(t, string(b), tc.responseBodyContains) |
|
}) |
|
} |
|
} |
|
|
|
func TestResourceWriteHandler(t *testing.T) { |
|
aclResolver := &svc.MockACLResolver{} |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistReadPolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV1ReadPolicy, demo.ArtistV2ReadPolicy), nil) |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistWritePolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV1WritePolicy, demo.ArtistV2WritePolicy), nil) |
|
|
|
builder := svctest.NewResourceServiceBuilder(). |
|
WithACLResolver(aclResolver). |
|
WithRegisterFns(demo.RegisterTypes) |
|
client := builder.Run(t) |
|
handler := NewHandler("/api", client, builder.Registry(), parseToken, hclog.NewNullLogger()) |
|
|
|
t.Run("should be blocked if the token is not authorized", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistReadPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode) |
|
}) |
|
|
|
var readRsp *pbresource.ReadResponse |
|
t.Run("should write to the resource backend", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
require.Equal(t, "Keith Urban", result["data"].(map[string]any)["name"]) |
|
require.Equal(t, "keith-urban", result["id"].(map[string]any)["name"]) |
|
|
|
var err error |
|
readRsp, err = client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ |
|
Id: &pbresource.ID{ |
|
Type: demo.TypeV2Artist, |
|
Tenancy: resource.DefaultNamespacedTenancy(), |
|
Name: "keith-urban", |
|
}, |
|
}) |
|
require.NoError(t, err) |
|
require.NotNil(t, readRsp.Resource) |
|
|
|
var artist pbdemov2.Artist |
|
require.NoError(t, readRsp.Resource.Data.UnmarshalTo(&artist)) |
|
require.Equal(t, "Keith Urban", artist.Name) |
|
}) |
|
|
|
t.Run("should update the record with version parameter", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", fmt.Sprintf("/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&version=%s", readRsp.Resource.Version), strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban Two", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
require.Equal(t, "Keith Urban Two", result["data"].(map[string]any)["name"]) |
|
require.Equal(t, "keith-urban", result["id"].(map[string]any)["name"]) |
|
}) |
|
|
|
t.Run("should fail the update if the resource's version doesn't match the version of the existing resource", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&version=1", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusConflict, rsp.Result().StatusCode) |
|
}) |
|
|
|
t.Run("should write to the resource backend with owner", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", "/demo/v1/artist/keith-urban-v1?partition=default&peer_name=local&namespace=default", strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "Keith Urban V1", |
|
"genre": "GENRE_COUNTRY" |
|
}, |
|
"owner": { |
|
"name": "keith-urban", |
|
"type": { |
|
"group": "demo", |
|
"group_version": "v2", |
|
"kind": "Artist" |
|
}, |
|
"tenancy": { |
|
"partition": "default", |
|
"namespace": "default" |
|
} |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
require.Equal(t, "Keith Urban V1", result["data"].(map[string]any)["name"]) |
|
require.Equal(t, "keith-urban-v1", result["id"].(map[string]any)["name"]) |
|
|
|
readRsp, err := client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ |
|
Id: &pbresource.ID{ |
|
Type: demo.TypeV1Artist, |
|
Tenancy: resource.DefaultNamespacedTenancy(), |
|
Name: "keith-urban-v1", |
|
}, |
|
}) |
|
require.NoError(t, err) |
|
require.NotNil(t, readRsp.Resource) |
|
require.Equal(t, "keith-urban", readRsp.Resource.Owner.Name) |
|
|
|
var artist pbdemov1.Artist |
|
require.NoError(t, readRsp.Resource.Data.UnmarshalTo(&artist)) |
|
require.Equal(t, "Keith Urban V1", artist.Name) |
|
}) |
|
} |
|
|
|
type ResourceUri struct { |
|
group string |
|
version string |
|
kind string |
|
resourceName string |
|
} |
|
|
|
func createResource(t *testing.T, artistHandler http.Handler, resourceUri *ResourceUri) map[string]any { |
|
rsp := httptest.NewRecorder() |
|
|
|
if resourceUri == nil { |
|
resourceUri = &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "keith-urban"} |
|
} |
|
|
|
req := httptest.NewRequest("PUT", fmt.Sprintf("/%s/%s/%s/%s?partition=default&peer_name=local&namespace=default", resourceUri.group, resourceUri.version, resourceUri.kind, resourceUri.resourceName), strings.NewReader(` |
|
{ |
|
"metadata": { |
|
"foo": "bar" |
|
}, |
|
"data": { |
|
"name": "test", |
|
"genre": "GENRE_COUNTRY" |
|
} |
|
} |
|
`)) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
artistHandler.ServeHTTP(rsp, req) |
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
return result |
|
} |
|
|
|
func deleteResource(t *testing.T, artistHandler http.Handler, resourceUri *ResourceUri) { |
|
rsp := httptest.NewRecorder() |
|
|
|
if resourceUri == nil { |
|
resourceUri = &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "keith-urban"} |
|
} |
|
|
|
req := httptest.NewRequest("DELETE", fmt.Sprintf("/%s/%s/%s/%s?partition=default&peer_name=local&namespace=default", resourceUri.group, resourceUri.version, resourceUri.kind, resourceUri.resourceName), strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
artistHandler.ServeHTTP(rsp, req) |
|
require.Equal(t, http.StatusNoContent, rsp.Result().StatusCode) |
|
} |
|
|
|
func TestResourceReadHandler(t *testing.T) { |
|
aclResolver := &svc.MockACLResolver{} |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistReadPolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV1ReadPolicy, demo.ArtistV2ReadPolicy), nil) |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistWritePolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV1WritePolicy, demo.ArtistV2WritePolicy), nil) |
|
aclResolver.On("ResolveTokenAndDefaultMeta", fakeToken, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, ""), nil) |
|
|
|
builder := svctest.NewResourceServiceBuilder(). |
|
WithRegisterFns(demo.RegisterTypes). |
|
WithACLResolver(aclResolver) |
|
client := builder.Run(t) |
|
handler := NewHandler("/api", client, builder.Registry(), parseToken, hclog.NewNullLogger()) |
|
|
|
createdResource := createResource(t, handler, nil) |
|
|
|
t.Run("Read resource", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&consistent", nil) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistReadPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
require.Equal(t, result, createdResource) |
|
}) |
|
|
|
t.Run("should not be found if resource not exist", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist/keith-not-exist?partition=default&peer_name=local&namespace=default&consistent", nil) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistReadPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusNotFound, rsp.Result().StatusCode) |
|
}) |
|
|
|
t.Run("should be blocked if the token is not authorized", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&consistent", nil) |
|
|
|
req.Header.Add("x-consul-token", fakeToken) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode) |
|
}) |
|
} |
|
|
|
func TestResourceDeleteHandler(t *testing.T) { |
|
aclResolver := &svc.MockACLResolver{} |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistReadPolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV2ReadPolicy), nil) |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistWritePolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV2WritePolicy), nil) |
|
|
|
builder := svctest.NewResourceServiceBuilder(). |
|
WithRegisterFns(demo.RegisterTypes). |
|
WithACLResolver(aclResolver) |
|
client := builder.Run(t) |
|
handler := NewHandler("/api", client, builder.Registry(), parseToken, hclog.NewNullLogger()) |
|
|
|
t.Run("should surface PermissionDenied error from resource service", func(t *testing.T) { |
|
createResource(t, handler, nil) |
|
|
|
deleteRsp := httptest.NewRecorder() |
|
deletReq := httptest.NewRequest("DELETE", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
deletReq.Header.Add("x-consul-token", testACLTokenArtistReadPolicy) |
|
|
|
handler.ServeHTTP(deleteRsp, deletReq) |
|
|
|
require.Equal(t, http.StatusForbidden, deleteRsp.Result().StatusCode) |
|
}) |
|
|
|
t.Run("should delete a resource without version", func(t *testing.T) { |
|
createResource(t, handler, nil) |
|
|
|
deleteRsp := httptest.NewRecorder() |
|
deletReq := httptest.NewRequest("DELETE", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
deletReq.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(deleteRsp, deletReq) |
|
|
|
require.Equal(t, http.StatusNoContent, deleteRsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(deleteRsp.Body).Decode(&result)) |
|
require.Empty(t, result) |
|
|
|
_, err := client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ |
|
Id: &pbresource.ID{ |
|
Type: demo.TypeV2Artist, |
|
Tenancy: resource.DefaultNamespacedTenancy(), |
|
Name: "keith-urban", |
|
}, |
|
}) |
|
require.ErrorContains(t, err, "resource not found") |
|
}) |
|
|
|
t.Run("should delete a resource with version", func(t *testing.T) { |
|
createResource(t, handler, nil) |
|
|
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("DELETE", "/demo/v2/artist/keith-urban?partition=default&peer_name=local&namespace=default&version=1", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusNoContent, rsp.Result().StatusCode) |
|
|
|
_, err := client.Read(testutil.TestContext(t), &pbresource.ReadRequest{ |
|
Id: &pbresource.ID{ |
|
Type: demo.TypeV2Artist, |
|
Tenancy: resource.DefaultNamespacedTenancy(), |
|
Name: "keith-urban", |
|
}, |
|
}) |
|
require.ErrorContains(t, err, "resource not found") |
|
}) |
|
} |
|
|
|
func TestResourceListHandler(t *testing.T) { |
|
aclResolver := &svc.MockACLResolver{} |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistListPolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV2ListPolicy), nil) |
|
aclResolver.On("ResolveTokenAndDefaultMeta", testACLTokenArtistWritePolicy, mock.Anything, mock.Anything). |
|
Return(svctest.AuthorizerFrom(t, demo.ArtistV2WritePolicy), nil) |
|
|
|
builder := svctest.NewResourceServiceBuilder(). |
|
WithRegisterFns(demo.RegisterTypes). |
|
WithACLResolver(aclResolver) |
|
client := builder.Run(t) |
|
handler := NewHandler("/api", client, builder.Registry(), parseToken, hclog.NewNullLogger()) |
|
|
|
t.Run("should return MethodNotAllowed", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("PUT", "/demo/v2/artist?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusMethodNotAllowed, rsp.Result().StatusCode) |
|
}) |
|
|
|
t.Run("should be blocked if the token is not authorized", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistWritePolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusForbidden, rsp.Result().StatusCode) |
|
}) |
|
|
|
t.Run("should return list of resources", func(t *testing.T) { |
|
resourceUri1 := &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "steve"} |
|
resource1 := createResource(t, handler, resourceUri1) |
|
resourceUri2 := &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "elvis"} |
|
resource2 := createResource(t, handler, resourceUri2) |
|
|
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
|
|
resources, _ := result["resources"].([]any) |
|
require.Len(t, resources, 2) |
|
|
|
expected := []map[string]any{resource1, resource2} |
|
require.Contains(t, expected, resources[0]) |
|
require.Contains(t, expected, resources[1]) |
|
|
|
// clean up |
|
deleteResource(t, handler, resourceUri1) |
|
deleteResource(t, handler, resourceUri2) |
|
}) |
|
|
|
t.Run("should return empty list when no resources are found", func(t *testing.T) { |
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist?partition=default&peer_name=local&namespace=default", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
|
|
resources, _ := result["resources"].([]any) |
|
require.Len(t, resources, 0) |
|
}) |
|
|
|
t.Run("should return empty list when name prefix matches don't match", func(t *testing.T) { |
|
createResource(t, handler, nil) |
|
|
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist?partition=default&peer_name=local&namespace=default&name_prefix=noname", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
|
|
resources, _ := result["resources"].([]any) |
|
require.Len(t, resources, 0) |
|
|
|
// clean up |
|
deleteResource(t, handler, nil) |
|
}) |
|
|
|
t.Run("should return list of resources matching name prefix", func(t *testing.T) { |
|
resourceUri1 := &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "steve"} |
|
resource1 := createResource(t, handler, resourceUri1) |
|
resourceUri2 := &ResourceUri{group: "demo", version: "v2", kind: "artist", resourceName: "elvis"} |
|
createResource(t, handler, resourceUri2) |
|
|
|
rsp := httptest.NewRecorder() |
|
req := httptest.NewRequest("GET", "/demo/v2/artist?partition=default&peer_name=local&namespace=default&name_prefix=steve", strings.NewReader("")) |
|
|
|
req.Header.Add("x-consul-token", testACLTokenArtistListPolicy) |
|
|
|
handler.ServeHTTP(rsp, req) |
|
|
|
require.Equal(t, http.StatusOK, rsp.Result().StatusCode) |
|
|
|
var result map[string]any |
|
require.NoError(t, json.NewDecoder(rsp.Body).Decode(&result)) |
|
|
|
resources, _ := result["resources"].([]any) |
|
require.Len(t, resources, 1) |
|
|
|
require.Equal(t, resource1, resources[0]) |
|
|
|
// clean up |
|
deleteResource(t, handler, resourceUri1) |
|
deleteResource(t, handler, resourceUri2) |
|
}) |
|
}
|
|
|