mirror of https://github.com/vernonkeenan/lib
435 lines
15 KiB
Go
435 lines
15 KiB
Go
package members_client_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_client/prompts"
|
|
"code.tnxs.net/vernonkeenan/lib/api/members/members_models"
|
|
openapiruntime "github.com/go-openapi/runtime"
|
|
"github.com/go-openapi/strfmt"
|
|
)
|
|
|
|
var promptTestAuth = openapiruntime.ClientAuthInfoWriterFunc(func(openapiruntime.ClientRequest, strfmt.Registry) error {
|
|
return nil
|
|
})
|
|
|
|
type promptCaptureTransport struct {
|
|
operation *openapiruntime.ClientOperation
|
|
}
|
|
|
|
func (transport *promptCaptureTransport) Submit(operation *openapiruntime.ClientOperation) (any, error) {
|
|
return transport.SubmitContext(context.Background(), operation)
|
|
}
|
|
|
|
func (transport *promptCaptureTransport) SubmitContext(_ context.Context, operation *openapiruntime.ClientOperation) (any, error) {
|
|
transport.operation = operation
|
|
|
|
switch operation.ID {
|
|
case "getPrompts":
|
|
return prompts.NewGetPromptsOK(), nil
|
|
case "postPrompts":
|
|
return prompts.NewPostPromptsOK(), nil
|
|
case "putPrompts":
|
|
return prompts.NewPutPromptsOK(), nil
|
|
case "getPromptAnswers":
|
|
return prompts.NewGetPromptAnswersOK(), nil
|
|
case "postPromptAnswers":
|
|
return prompts.NewPostPromptAnswersOK(), nil
|
|
case "getPromptCategories":
|
|
return prompts.NewGetPromptCategoriesOK(), nil
|
|
case "postPromptCategories":
|
|
return prompts.NewPostPromptCategoriesOK(), nil
|
|
case "putPromptCategories":
|
|
return prompts.NewPutPromptCategoriesOK(), nil
|
|
case "getPromptTags":
|
|
return prompts.NewGetPromptTagsOK(), nil
|
|
case "postPromptTags":
|
|
return prompts.NewPostPromptTagsOK(), nil
|
|
case "putPromptTags":
|
|
return prompts.NewPutPromptTagsOK(), nil
|
|
default:
|
|
panic("unexpected generated prompt operation: " + operation.ID)
|
|
}
|
|
}
|
|
|
|
func TestGeneratedPromptOperationContract(t *testing.T) {
|
|
type operationCall func(openapiruntime.ContextualTransport) error
|
|
|
|
tests := []struct {
|
|
name string
|
|
wantID string
|
|
wantMethod string
|
|
wantPath string
|
|
call operationCall
|
|
}{
|
|
{
|
|
name: "list prompts", wantID: "getPrompts", wantMethod: "GET", wantPath: "/prompts",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).GetPrompts(prompts.NewGetPromptsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create prompt", wantID: "postPrompts", wantMethod: "POST", wantPath: "/prompts",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PostPrompts(prompts.NewPostPromptsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update prompt", wantID: "putPrompts", wantMethod: "PUT", wantPath: "/prompts",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PutPrompts(prompts.NewPutPromptsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "list prompt answers", wantID: "getPromptAnswers", wantMethod: "GET", wantPath: "/promptanswers",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).GetPromptAnswers(prompts.NewGetPromptAnswersParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "append prompt answer", wantID: "postPromptAnswers", wantMethod: "POST", wantPath: "/promptanswers",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PostPromptAnswers(prompts.NewPostPromptAnswersParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "list prompt categories", wantID: "getPromptCategories", wantMethod: "GET", wantPath: "/promptcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).GetPromptCategories(prompts.NewGetPromptCategoriesParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create prompt category", wantID: "postPromptCategories", wantMethod: "POST", wantPath: "/promptcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PostPromptCategories(prompts.NewPostPromptCategoriesParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update prompt category", wantID: "putPromptCategories", wantMethod: "PUT", wantPath: "/promptcategories",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PutPromptCategories(prompts.NewPutPromptCategoriesParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "list prompt tags", wantID: "getPromptTags", wantMethod: "GET", wantPath: "/prompttags",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).GetPromptTags(prompts.NewGetPromptTagsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "create prompt tag", wantID: "postPromptTags", wantMethod: "POST", wantPath: "/prompttags",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PostPromptTags(prompts.NewPostPromptTagsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "update prompt tag", wantID: "putPromptTags", wantMethod: "PUT", wantPath: "/prompttags",
|
|
call: func(transport openapiruntime.ContextualTransport) error {
|
|
_, err := prompts.New(transport, strfmt.Default).PutPromptTags(prompts.NewPutPromptTagsParams(), promptTestAuth)
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
transport := &promptCaptureTransport{}
|
|
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 TestPromptSpecRequiresExactCompoundAuthentication(t *testing.T) {
|
|
specText := readMembersPromptSpec(t)
|
|
for _, definition := range []string{
|
|
" ApiKeyAuth:\n type: \"apiKey\"\n in: \"header\"\n name: \"X-API-Key\"",
|
|
" kvSessionCookie:\n name: Cookie\n in: header\n type: apiKey",
|
|
} {
|
|
if !strings.Contains(specText, definition) {
|
|
t.Fatalf("authoritative spec is missing exact authentication definition:\n%s", definition)
|
|
}
|
|
}
|
|
|
|
operations := map[string]map[string]string{
|
|
"/prompts": {
|
|
"GET": "getPrompts",
|
|
"POST": "postPrompts",
|
|
"PUT": "putPrompts",
|
|
},
|
|
"/promptanswers": {
|
|
"GET": "getPromptAnswers",
|
|
"POST": "postPromptAnswers",
|
|
},
|
|
"/promptcategories": {
|
|
"GET": "getPromptCategories",
|
|
"POST": "postPromptCategories",
|
|
"PUT": "putPromptCategories",
|
|
},
|
|
"/prompttags": {
|
|
"GET": "getPromptTags",
|
|
"POST": "postPromptTags",
|
|
"PUT": "putPromptTags",
|
|
},
|
|
}
|
|
|
|
for path, methods := range operations {
|
|
for method, operationID := range methods {
|
|
operation := promptSpecOperationBlock(t, specText, path, strings.ToLower(method))
|
|
if !strings.Contains(operation, " operationId: "+operationID+"\n") {
|
|
t.Errorf("%s %s does not preserve operationId %q", method, path, operationID)
|
|
}
|
|
const compoundSecurity = " security:\n - ApiKeyAuth: []\n kvSessionCookie: []"
|
|
if strings.Count(operation, compoundSecurity) != 1 {
|
|
t.Errorf("%s %s does not require the exact API-key + human-session compound security", method, path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPromptModelHasExactFullFieldParity(t *testing.T) {
|
|
want := map[string]string{
|
|
"CreatedByID": "*string",
|
|
"CreatedDate": "*string",
|
|
"ID": "string",
|
|
"Icon": "*string",
|
|
"ImageAltText": "*string",
|
|
"ImageURL": "*string",
|
|
"LastModifiedByID": "*string",
|
|
"LastModifiedDate": "*string",
|
|
"Logo": "*string",
|
|
"MaxTokens": "*float64",
|
|
"Model": "*string",
|
|
"Name": "*string",
|
|
"Order": "*float64",
|
|
"Parameters": "*string",
|
|
"Prompt": "*string",
|
|
"PromptCategoryID": "*string",
|
|
"ResearchProjectIDs": "[]string",
|
|
"Slug": "*string",
|
|
"System": "*string",
|
|
"Tags": "[]string",
|
|
"Temperature": "*float64",
|
|
"TenantID": "*string",
|
|
"Title": "*string",
|
|
"UsedCount": "*float64",
|
|
"UserID": "*string",
|
|
}
|
|
|
|
model := reflect.TypeOf(members_models.Prompt{})
|
|
if model.NumField() != len(want) {
|
|
t.Fatalf("Prompt has %d fields, want exact authoritative set of %d", model.NumField(), len(want))
|
|
}
|
|
for name, wantType := range want {
|
|
field, exists := model.FieldByName(name)
|
|
if !exists {
|
|
t.Errorf("Prompt is missing authoritative field %s", name)
|
|
continue
|
|
}
|
|
if field.Type.String() != wantType {
|
|
t.Errorf("Prompt.%s type = %s, want %s", name, field.Type, wantType)
|
|
}
|
|
if got := strings.Split(field.Tag.Get("json"), ",")[0]; got != name {
|
|
t.Errorf("Prompt.%s JSON name = %q, want %q", name, got, name)
|
|
}
|
|
}
|
|
|
|
maxTokens := 4096.0
|
|
body, err := json.Marshal(members_models.Prompt{MaxTokens: &maxTokens})
|
|
if err != nil {
|
|
t.Fatalf("marshal Prompt: %v", err)
|
|
}
|
|
if !bytes.Contains(body, []byte(`"MaxTokens":4096`)) {
|
|
t.Fatalf("Prompt JSON lost MaxTokens: %s", body)
|
|
}
|
|
}
|
|
|
|
func TestPromptAnswerIsAppendOnlyAndLegacyMisspellingIsAbsent(t *testing.T) {
|
|
contract := reflect.TypeOf((*prompts.ClientService)(nil)).Elem()
|
|
for _, method := range []string{
|
|
"PutPromptAnswer",
|
|
"PutPromptAnswerContext",
|
|
"PutPromptAnswers",
|
|
"PutPromptAnswersContext",
|
|
"PutPromptAnsweer",
|
|
"PutPromptAnsweerContext",
|
|
"PutPromptAnsweers",
|
|
"PutPromptAnsweersContext",
|
|
} {
|
|
if _, exists := contract.MethodByName(method); exists {
|
|
t.Errorf("append-only PromptAnswer client exposes forbidden update %s", method)
|
|
}
|
|
}
|
|
|
|
answerPath := promptSpecPathBlock(t, readMembersPromptSpec(t), "/promptanswers")
|
|
for _, forbidden := range []string{" put:", " patch:", " delete:"} {
|
|
if strings.Contains(answerPath, forbidden) {
|
|
t.Fatalf("authoritative PromptAnswer contract exposes forbidden operation %q", strings.TrimSpace(forbidden))
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPromptUpdateClientsPreserveConflictResponses(t *testing.T) {
|
|
conflicts := []interface{ IsCode(int) bool }{
|
|
prompts.NewPutPromptsConflict(),
|
|
prompts.NewPutPromptCategoriesConflict(),
|
|
prompts.NewPutPromptTagsConflict(),
|
|
}
|
|
for _, conflict := range conflicts {
|
|
if !conflict.IsCode(409) {
|
|
t.Errorf("%T does not preserve the optimistic-conflict response", conflict)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPromptProviderContractKeepsExternalMembersSpecAligned(t *testing.T) {
|
|
repoRoot := promptRepoRoot(t)
|
|
mainSpec, err := os.ReadFile(filepath.Join(repoRoot, "swagger", "members-vernonkeenan.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("read Members spec: %v", err)
|
|
}
|
|
|
|
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 host/scheme/base-path rewrite")
|
|
}
|
|
}
|
|
|
|
func TestGeneratedPromptSurfaceHasNoSalesforceOrEmbeddedSecrets(t *testing.T) {
|
|
repoRoot := promptRepoRoot(t)
|
|
targets := []string{
|
|
filepath.Join(repoRoot, "api", "members", "members_client", "prompts"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_response.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_answer_response.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_category_response.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag_request.go"),
|
|
filepath.Join(repoRoot, "api", "members", "members_models", "prompt_tag_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 prompt target %s: %v", target, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func readMembersPromptSpec(t *testing.T) string {
|
|
t.Helper()
|
|
content, err := os.ReadFile(filepath.Join(promptRepoRoot(t), "swagger", "members-vernonkeenan.yaml"))
|
|
if err != nil {
|
|
t.Fatalf("read Members Swagger: %v", err)
|
|
}
|
|
return string(content)
|
|
}
|
|
|
|
func promptSpecPathBlock(t *testing.T, specText, path string) string {
|
|
t.Helper()
|
|
startMarker := " " + path + ":\n"
|
|
start := strings.Index(specText, startMarker)
|
|
if start < 0 {
|
|
t.Fatalf("authoritative spec is missing path %s", path)
|
|
}
|
|
block := specText[start+len(startMarker):]
|
|
if end := strings.Index(block, "\n /"); end >= 0 {
|
|
block = block[:end]
|
|
}
|
|
return block
|
|
}
|
|
|
|
func promptSpecOperationBlock(t *testing.T, specText, path, method string) string {
|
|
t.Helper()
|
|
pathBlock := promptSpecPathBlock(t, specText, path)
|
|
startMarker := " " + method + ":\n"
|
|
start := strings.Index(pathBlock, startMarker)
|
|
if start < 0 {
|
|
t.Fatalf("authoritative spec is missing %s %s", strings.ToUpper(method), path)
|
|
}
|
|
block := pathBlock[start+len(startMarker):]
|
|
methodBoundary := regexp.MustCompile(`(?m)^ [a-z]+:\n`)
|
|
if end := methodBoundary.FindStringIndex(block); end != nil {
|
|
block = block[:end[0]]
|
|
}
|
|
return block
|
|
}
|
|
|
|
func promptRepoRoot(t *testing.T) string {
|
|
t.Helper()
|
|
_, testFile, _, ok := runtime.Caller(0)
|
|
if !ok {
|
|
t.Fatal("resolve test source path")
|
|
}
|
|
return filepath.Clean(filepath.Join(filepath.Dir(testFile), "..", "..", ".."))
|
|
}
|