mirror of https://github.com/vernonkeenan/lib
273 lines
9.0 KiB
Go
273 lines
9.0 KiB
Go
package members_client_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_client"
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_client/tracks"
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_models"
|
|
openapiruntime "github.com/go-openapi/runtime"
|
|
"github.com/go-openapi/strfmt"
|
|
)
|
|
|
|
const membersTrackSpecSHA256 = "4a5fd893c6490c18c6fa6fe3ddb234caf7cbfebb04a1c974fbf16854c2089acf"
|
|
|
|
var trackTestAuth = openapiruntime.ClientAuthInfoWriterFunc(
|
|
func(openapiruntime.ClientRequest, strfmt.Registry) error { return nil },
|
|
)
|
|
|
|
type trackCaptureTransport struct {
|
|
operation *openapiruntime.ClientOperation
|
|
}
|
|
|
|
func (transport *trackCaptureTransport) Submit(
|
|
operation *openapiruntime.ClientOperation,
|
|
) (any, error) {
|
|
return transport.SubmitContext(context.Background(), operation)
|
|
}
|
|
|
|
func (transport *trackCaptureTransport) SubmitContext(
|
|
_ context.Context,
|
|
operation *openapiruntime.ClientOperation,
|
|
) (any, error) {
|
|
transport.operation = operation
|
|
switch operation.ID {
|
|
case "getTracks":
|
|
return tracks.NewGetTracksOK(), nil
|
|
case "postTracks":
|
|
return tracks.NewPostTracksOK(), nil
|
|
case "putTracks":
|
|
return tracks.NewPutTracksOK(), nil
|
|
default:
|
|
panic("unexpected generated Track operation: " + operation.ID)
|
|
}
|
|
}
|
|
|
|
func TestGeneratedTrackOperationContract(t *testing.T) {
|
|
type operationCall func(openapiruntime.ContextualTransport) error
|
|
tests := []struct {
|
|
name, wantID, wantMethod, wantPath string
|
|
call operationCall
|
|
}{
|
|
{
|
|
name: "list or get Track", wantID: "getTracks",
|
|
wantMethod: "GET", wantPath: "/tracks",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := tracks.New(transport, strfmt.Default).GetTracks(
|
|
tracks.NewGetTracksParams(), trackTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create Track", wantID: "postTracks",
|
|
wantMethod: "POST", wantPath: "/tracks",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := tracks.New(transport, strfmt.Default).PostTracks(
|
|
tracks.NewPostTracksParams(), trackTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "CAS update Track", wantID: "putTracks",
|
|
wantMethod: "PUT", wantPath: "/tracks",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := tracks.New(transport, strfmt.Default).PutTracks(
|
|
tracks.NewPutTracksParams(), trackTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
transport := &trackCaptureTransport{}
|
|
if err := test.call(transport); err != nil {
|
|
t.Fatalf("generated client operation failed: %v", err)
|
|
}
|
|
if transport.operation == nil {
|
|
t.Fatal("generated client did not submit an operation")
|
|
}
|
|
if transport.operation.ID != test.wantID {
|
|
t.Errorf("operation ID = %q, want %q", transport.operation.ID, test.wantID)
|
|
}
|
|
if transport.operation.Method != test.wantMethod {
|
|
t.Errorf("method = %q, want %q", transport.operation.Method, test.wantMethod)
|
|
}
|
|
if transport.operation.PathPattern != test.wantPath {
|
|
t.Errorf("path = %q, want %q", transport.operation.PathPattern, test.wantPath)
|
|
}
|
|
if transport.operation.AuthInfo == nil {
|
|
t.Error("generated operation discarded its compound auth-info writer")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTrackSpecPreservesExactScopesCompoundAuthAndCAS(t *testing.T) {
|
|
specText := readMembersLearningSpec(t)
|
|
pathBlock := learningSpecPathBlock(t, specText, "/tracks")
|
|
for _, forbidden := range []string{" delete:", " patch:"} {
|
|
if strings.Contains(pathBlock, forbidden) {
|
|
t.Errorf("/tracks exposes unsupported %s", strings.TrimSpace(forbidden))
|
|
}
|
|
}
|
|
operations := map[string]struct {
|
|
id, scope string
|
|
}{
|
|
"get": {id: "getTracks", scope: "members:track:read"},
|
|
"post": {id: "postTracks", scope: "members:track:create"},
|
|
"put": {id: "putTracks", scope: "members:track:update"},
|
|
}
|
|
const compoundSecurity = " security:\n - ApiKeyAuth: []\n kvSessionCookie: []"
|
|
for method, expected := range operations {
|
|
block := learningSpecOperationBlock(t, specText, "/tracks", method)
|
|
if !strings.Contains(block, " operationId: "+expected.id+"\n") {
|
|
t.Errorf("%s /tracks lost operationId %q", strings.ToUpper(method), expected.id)
|
|
}
|
|
if strings.Count(block, compoundSecurity) != 1 {
|
|
t.Errorf("%s /tracks does not preserve compound authentication", strings.ToUpper(method))
|
|
}
|
|
if !strings.Contains(block, expected.scope) {
|
|
t.Errorf("%s /tracks lost exact scope %q", strings.ToUpper(method), expected.scope)
|
|
}
|
|
}
|
|
if !tracks.NewPutTracksConflict().IsCode(409) {
|
|
t.Fatal("Track update client lost the optimistic concurrency response")
|
|
}
|
|
recordID := "track-id"
|
|
if got := tracks.NewGetTracksParams().WithID(&recordID).ID; got == nil || *got != recordID {
|
|
t.Fatal("Track list/get client lost its ID filter")
|
|
}
|
|
}
|
|
|
|
func TestTrackModelAndSingleRecordRequestAreBounded(t *testing.T) {
|
|
assertExactModelFields(t, members_models.Track{}, map[string]string{
|
|
"CreatedByID": "*string", "CreatedDate": "*string", "Description": "*string",
|
|
"ID": "string", "ImageAltText": "*string", "ImageURL": "*string",
|
|
"LastModifiedByID": "*string", "LastModifiedDate": "*string",
|
|
"Logo": "*string", "Name": "string", "Slug": "string",
|
|
})
|
|
request := &members_models.TrackRequest{
|
|
Data: []*members_models.Track{{Name: "One", Slug: "one"}, {Name: "Two", Slug: "two"}},
|
|
}
|
|
if err := request.Validate(strfmt.Default); err == nil {
|
|
t.Fatal("generated Track request accepted a bulk payload")
|
|
}
|
|
}
|
|
|
|
func TestTrackSurfaceSeparatesBoundedRelationshipClients(t *testing.T) {
|
|
contract := reflect.TypeOf((*tracks.ClientService)(nil)).Elem()
|
|
for index := 0; index < contract.NumMethod(); index++ {
|
|
method := contract.Method(index).Name
|
|
for _, forbidden := range []string{"Delete", "TrackEvent", "TrackTopic", "TrackUser"} {
|
|
if strings.Contains(method, forbidden) {
|
|
t.Errorf("Track client exposes unsafe method %s", method)
|
|
}
|
|
}
|
|
}
|
|
|
|
root := reflect.TypeOf(members_client.Members{})
|
|
if _, exists := root.FieldByName("Tracks"); !exists {
|
|
t.Fatal("root Members client does not expose governed Tracks")
|
|
}
|
|
if _, exists := root.FieldByName("TrackEvents"); !exists {
|
|
t.Fatal("root Members client does not expose governed TrackEvents")
|
|
}
|
|
for _, required := range []string{"TrackTopics", "TrackUsers"} {
|
|
if _, exists := root.FieldByName(required); !exists {
|
|
t.Errorf("root Members client omits governed %s", required)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMembersSpecIsPinnedToTrackProviderCommit(t *testing.T) {
|
|
repoRoot := learningRepoRoot(t)
|
|
mainSpec, err := os.ReadFile(filepath.Join(
|
|
repoRoot, "swagger", "members-vernonkeenan.yaml",
|
|
))
|
|
if err != nil {
|
|
t.Fatalf("read Members spec: %v", err)
|
|
}
|
|
sum := sha256.Sum256(mainSpec)
|
|
if got := hex.EncodeToString(sum[:]); got != membersTrackSpecSHA256 {
|
|
t.Fatalf(
|
|
"Members spec drifted from the integrated TrackEvent, Transaction, TrackTopic, TrackUser, and Database metadata provider contract: SHA-256 = %s, want %s",
|
|
got, membersTrackSpecSHA256,
|
|
)
|
|
}
|
|
|
|
externalSpec, err := os.ReadFile(filepath.Join(
|
|
repoRoot, "swagger", "external", "members-vernonkeenan.yaml",
|
|
))
|
|
if err != nil {
|
|
t.Fatalf("read external Members spec: %v", err)
|
|
}
|
|
normalized := bytes.ReplaceAll(externalSpec, []byte(`"https"`), []byte(`"http"`))
|
|
normalized = bytes.ReplaceAll(
|
|
normalized, []byte("gw.tnxs.net"), []byte("members.vernonkeenan.com:8080"),
|
|
)
|
|
normalized = bytes.ReplaceAll(
|
|
normalized, []byte(`"/vk/members/v1"`), []byte(`"/v1"`),
|
|
)
|
|
if !bytes.Equal(normalized, mainSpec) {
|
|
t.Fatal("external Members spec differs from the conventional gateway rewrite")
|
|
}
|
|
}
|
|
|
|
func TestGeneratedTrackSurfaceHasNoExternalBypassOrSecrets(t *testing.T) {
|
|
repoRoot := learningRepoRoot(t)
|
|
targets := []string{
|
|
filepath.Join(repoRoot, "api", "members", "members_client", "tracks"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "track.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "track_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "track_response.go"),
|
|
}
|
|
credentialLiteral := regexp.MustCompile(
|
|
`(?i)(api[_-]?key|password|secret)\s*[:=]\s*["'][^"']+["']`,
|
|
)
|
|
for _, target := range targets {
|
|
err := filepath.WalkDir(target, func(
|
|
path string, entry os.DirEntry, walkErr error,
|
|
) error {
|
|
if walkErr != nil {
|
|
return walkErr
|
|
}
|
|
if entry.IsDir() || !strings.HasSuffix(path, ".go") ||
|
|
strings.HasSuffix(path, "_test.go") {
|
|
return nil
|
|
}
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
lower := strings.ToLower(string(content))
|
|
for _, forbidden := range []string{
|
|
"salesforce", "sf-gate", "go-force", "private key-----",
|
|
} {
|
|
if strings.Contains(lower, forbidden) {
|
|
t.Errorf("%s contains forbidden boundary %q", path, forbidden)
|
|
}
|
|
}
|
|
if credentialLiteral.Match(content) {
|
|
t.Errorf("%s contains a credential-shaped literal", path)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("scan generated Track target %s: %v", target, err)
|
|
}
|
|
}
|
|
}
|