mirror of https://github.com/vernonkeenan/lib
363 lines
12 KiB
Go
363 lines
12 KiB
Go
package members_client_test
|
|
|
|
import (
|
|
"context"
|
|
"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/attendees"
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_client/events"
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_models"
|
|
openapiruntime "github.com/go-openapi/runtime"
|
|
"github.com/go-openapi/strfmt"
|
|
)
|
|
|
|
var eventManagementTestAuth = openapiruntime.ClientAuthInfoWriterFunc(
|
|
func(openapiruntime.ClientRequest, strfmt.Registry) error { return nil },
|
|
)
|
|
|
|
type eventManagementCaptureTransport struct {
|
|
operation *openapiruntime.ClientOperation
|
|
}
|
|
|
|
func (transport *eventManagementCaptureTransport) Submit(
|
|
operation *openapiruntime.ClientOperation,
|
|
) (any, error) {
|
|
return transport.SubmitContext(context.Background(), operation)
|
|
}
|
|
|
|
func (transport *eventManagementCaptureTransport) SubmitContext(
|
|
_ context.Context,
|
|
operation *openapiruntime.ClientOperation,
|
|
) (any, error) {
|
|
transport.operation = operation
|
|
switch operation.ID {
|
|
case "getEventCategories":
|
|
return events.NewGetEventCategoriesOK(), nil
|
|
case "postEventCategories":
|
|
return events.NewPostEventCategoriesOK(), nil
|
|
case "putEventCategories":
|
|
return events.NewPutEventCategoriesOK(), nil
|
|
case "getEvents":
|
|
return events.NewGetEventsOK(), nil
|
|
case "postEvents":
|
|
return events.NewPostEventsOK(), nil
|
|
case "putEvents":
|
|
return events.NewPutEventsOK(), nil
|
|
case "getAttendees":
|
|
return attendees.NewGetAttendeesOK(), nil
|
|
case "postAttendees":
|
|
return attendees.NewPostAttendeesOK(), nil
|
|
case "putAttendees":
|
|
return attendees.NewPutAttendeesOK(), nil
|
|
default:
|
|
panic("unexpected generated Event Management operation: " + operation.ID)
|
|
}
|
|
}
|
|
|
|
func TestGeneratedEventManagementOperationContract(t *testing.T) {
|
|
type operationCall func(openapiruntime.ContextualTransport) error
|
|
|
|
tests := []struct {
|
|
name string
|
|
wantID string
|
|
wantMethod string
|
|
wantPath string
|
|
call operationCall
|
|
}{
|
|
{
|
|
name: "list or get event categories", wantID: "getEventCategories",
|
|
wantMethod: "GET", wantPath: "/eventcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).GetEventCategories(
|
|
events.NewGetEventCategoriesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create event category", wantID: "postEventCategories",
|
|
wantMethod: "POST", wantPath: "/eventcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).PostEventCategories(
|
|
events.NewPostEventCategoriesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update event category", wantID: "putEventCategories",
|
|
wantMethod: "PUT", wantPath: "/eventcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).PutEventCategories(
|
|
events.NewPutEventCategoriesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "list or get events", wantID: "getEvents",
|
|
wantMethod: "GET", wantPath: "/events",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).GetEvents(
|
|
events.NewGetEventsParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create event", wantID: "postEvents",
|
|
wantMethod: "POST", wantPath: "/events",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).PostEvents(
|
|
events.NewPostEventsParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update event", wantID: "putEvents",
|
|
wantMethod: "PUT", wantPath: "/events",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := events.New(transport, strfmt.Default).PutEvents(
|
|
events.NewPutEventsParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "list or get attendees", wantID: "getAttendees",
|
|
wantMethod: "GET", wantPath: "/attendees",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := attendees.New(transport, strfmt.Default).GetAttendees(
|
|
attendees.NewGetAttendeesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create attendee", wantID: "postAttendees",
|
|
wantMethod: "POST", wantPath: "/attendees",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := attendees.New(transport, strfmt.Default).PostAttendees(
|
|
attendees.NewPostAttendeesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update attendee", wantID: "putAttendees",
|
|
wantMethod: "PUT", wantPath: "/attendees",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := attendees.New(transport, strfmt.Default).PutAttendees(
|
|
attendees.NewPutAttendeesParams(), eventManagementTestAuth,
|
|
)
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
transport := &eventManagementCaptureTransport{}
|
|
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 auth-info writer")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEventManagementSpecRequiresCompoundAuthenticationAndExactPaths(t *testing.T) {
|
|
specText := readMembersLearningSpec(t)
|
|
operations := map[string]map[string]string{
|
|
"/eventcategories": {
|
|
"get": "getEventCategories", "post": "postEventCategories",
|
|
"put": "putEventCategories",
|
|
},
|
|
"/events": {
|
|
"get": "getEvents", "post": "postEvents", "put": "putEvents",
|
|
},
|
|
"/attendees": {
|
|
"get": "getAttendees", "post": "postAttendees", "put": "putAttendees",
|
|
},
|
|
}
|
|
const compoundSecurity = " security:\n - ApiKeyAuth: []\n kvSessionCookie: []"
|
|
for path, methods := range operations {
|
|
pathBlock := learningSpecPathBlock(t, specText, path)
|
|
for _, forbidden := range []string{" delete:", " patch:"} {
|
|
if strings.Contains(pathBlock, forbidden) {
|
|
t.Errorf("%s exposes unsupported %s", path, strings.TrimSpace(forbidden))
|
|
}
|
|
}
|
|
for method, operationID := range methods {
|
|
operation := learningSpecOperationBlock(t, specText, path, method)
|
|
if !strings.Contains(operation, " operationId: "+operationID+"\n") {
|
|
t.Errorf("%s %s lost operationId %q", strings.ToUpper(method), path, operationID)
|
|
}
|
|
if strings.Count(operation, compoundSecurity) != 1 {
|
|
t.Errorf("%s %s does not preserve compound authentication", strings.ToUpper(method), path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventManagementGetByIDAndCASContracts(t *testing.T) {
|
|
recordID := "record-id"
|
|
filters := []struct {
|
|
name string
|
|
got *string
|
|
}{
|
|
{name: "EventCategory", got: events.NewGetEventCategoriesParams().WithID(&recordID).ID},
|
|
{name: "Event", got: events.NewGetEventsParams().WithID(&recordID).ID},
|
|
{name: "Attendee", got: attendees.NewGetAttendeesParams().WithID(&recordID).ID},
|
|
}
|
|
for _, filter := range filters {
|
|
if filter.got == nil || *filter.got != recordID {
|
|
t.Errorf("%s get-by-ID filter is absent", filter.name)
|
|
}
|
|
}
|
|
|
|
conflicts := []interface{ IsCode(int) bool }{
|
|
events.NewPutEventCategoriesConflict(),
|
|
events.NewPutEventsConflict(),
|
|
attendees.NewPutAttendeesConflict(),
|
|
}
|
|
for _, conflict := range conflicts {
|
|
if !conflict.IsCode(409) {
|
|
t.Errorf("%T does not preserve optimistic concurrency conflict", conflict)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestEventManagementModelsAreBoundedToProviderContract(t *testing.T) {
|
|
assertExactModelFields(t, members_models.EventCategory{}, 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",
|
|
})
|
|
assertExactModelFields(t, members_models.Event{}, map[string]string{
|
|
"AccountID": "*string", "Capacity": "*int64", "CreatedByID": "*string",
|
|
"CreatedDate": "*string", "Description": "*string", "EndDate": "*string",
|
|
"EventCategory": "*members_models.EventCategory", "EventCategoryID": "*string",
|
|
"ID": "string", "ImageAltText": "*string", "ImageURL": "*string",
|
|
"LastModifiedByID": "*string", "LastModifiedDate": "*string",
|
|
"Location": "*string", "Logo": "*string", "Online": "*bool",
|
|
"OrganizerUserID": "*string", "RegURL": "*string", "Slug": "*string",
|
|
"StartDate": "*string", "Title": "*string",
|
|
})
|
|
assertExactModelFields(t, members_models.Attendee{}, map[string]string{
|
|
"CheckinStatus": "int64", "CreatedDate": "*string", "EventID": "*string",
|
|
"ID": "string", "LastModifiedDate": "*string", "TicketID": "*string",
|
|
"UserID": "*string",
|
|
})
|
|
}
|
|
|
|
func TestEventManagementOmitsTicketAndDeleteClients(t *testing.T) {
|
|
contracts := []struct {
|
|
name string
|
|
typ reflect.Type
|
|
}{
|
|
{name: "Events", typ: reflect.TypeOf((*events.ClientService)(nil)).Elem()},
|
|
{name: "Attendees", typ: reflect.TypeOf((*attendees.ClientService)(nil)).Elem()},
|
|
}
|
|
for _, contract := range contracts {
|
|
for index := 0; index < contract.typ.NumMethod(); index++ {
|
|
method := contract.typ.Method(index).Name
|
|
if strings.HasPrefix(method, "Delete") || strings.Contains(method, "Ticket") {
|
|
t.Errorf("%s client exposes unsafe alias %s", contract.name, method)
|
|
}
|
|
}
|
|
}
|
|
|
|
root := reflect.TypeOf(members_client.Members{})
|
|
if _, exists := root.FieldByName("Tickets"); exists {
|
|
t.Fatal("root Members client exposes provider-pending Tickets")
|
|
}
|
|
|
|
repoRoot := learningRepoRoot(t)
|
|
removed := []string{
|
|
filepath.Join(repoRoot, "api", "members", "members_client", "tickets"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "ticket.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "ticket_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "ticket_response.go"),
|
|
}
|
|
for _, path := range removed {
|
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
t.Errorf("provider-pending Ticket artifact still exists: %s", path)
|
|
}
|
|
}
|
|
|
|
ticketPath := learningSpecPathBlock(t, readMembersLearningSpec(t), "/tickets")
|
|
if !strings.Contains(ticketPath, "x-provider-state: pending") {
|
|
t.Fatal("authoritative spec lost the Ticket provider-pending marker")
|
|
}
|
|
if strings.Contains(ticketPath, " delete:") {
|
|
t.Fatal("authoritative pending Ticket path exposes delete")
|
|
}
|
|
}
|
|
|
|
func TestGeneratedEventManagementSurfaceHasNoExternalBypassOrSecrets(t *testing.T) {
|
|
repoRoot := learningRepoRoot(t)
|
|
targets := []string{
|
|
filepath.Join(repoRoot, "api", "members", "members_client", "events"),
|
|
filepath.Join(repoRoot, "api", "members", "members_client", "attendees"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "event.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "event_category.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "attendee.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 Event Management target %s: %v", target, err)
|
|
}
|
|
}
|
|
}
|