package members_client_test import ( "bytes" "context" "crypto/sha256" "encoding/hex" "encoding/json" "os" "path/filepath" "reflect" "regexp" "runtime" "strings" "testing" "code.tnxs.net/vernonkeenan/lib/api/members/members_client/courses" "code.tnxs.net/vernonkeenan/lib/api/members/members_client/enrollments" "code.tnxs.net/vernonkeenan/lib/api/members/members_client/issued_certificates" "code.tnxs.net/vernonkeenan/lib/api/members/members_client/lesson_progress" "code.tnxs.net/vernonkeenan/lib/api/members/members_models" openapiruntime "github.com/go-openapi/runtime" "github.com/go-openapi/strfmt" ) const membersLearningSpecSHA256 = "546c86af359490c898a2ab4fad2e0e96e9051fd854a5fcd07a56093a4121a891" var learningTestAuth = openapiruntime.ClientAuthInfoWriterFunc(func(openapiruntime.ClientRequest, strfmt.Registry) error { return nil }) type learningCaptureTransport struct { operation *openapiruntime.ClientOperation } func (transport *learningCaptureTransport) Submit(operation *openapiruntime.ClientOperation) (any, error) { return transport.SubmitContext(context.Background(), operation) } func (transport *learningCaptureTransport) SubmitContext(_ context.Context, operation *openapiruntime.ClientOperation) (any, error) { transport.operation = operation switch operation.ID { case "getCourses": return courses.NewGetCoursesOK(), nil case "postCourses": return courses.NewPostCoursesOK(), nil case "updateCourses": return courses.NewUpdateCoursesOK(), nil case "getCourseSections": return courses.NewGetCourseSectionsOK(), nil case "postCourseSections": return courses.NewPostCourseSectionsOK(), nil case "updateCourseSections": return courses.NewUpdateCourseSectionsOK(), nil case "getCourseLessons": return courses.NewGetCourseLessonsOK(), nil case "postCourseLessons": return courses.NewPostCourseLessonsOK(), nil case "updateCourseLessons": return courses.NewUpdateCourseLessonsOK(), nil case "getEnrollments": return enrollments.NewGetEnrollmentsOK(), nil case "postEnrollments": return enrollments.NewPostEnrollmentsOK(), nil case "putEnrollments": return enrollments.NewPutEnrollmentsOK(), nil case "getLessonProgresses": return lesson_progress.NewGetLessonProgressesOK(), nil case "postLessonProgresses": return lesson_progress.NewPostLessonProgressesOK(), nil case "putLessonProgresses": return lesson_progress.NewPutLessonProgressesOK(), nil case "getIssuedCertificates": return issued_certificates.NewGetIssuedCertificatesOK(), nil default: panic("unexpected generated Learning operation: " + operation.ID) } } func TestGeneratedLearningOperationContract(t *testing.T) { type operationCall func(openapiruntime.ContextualTransport) error tests := []struct { name string wantID string wantMethod string wantPath string call operationCall }{ { name: "list courses", wantID: "getCourses", wantMethod: "GET", wantPath: "/courses", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).GetCourses(courses.NewGetCoursesParams(), learningTestAuth) return err }, }, { name: "create course", wantID: "postCourses", wantMethod: "POST", wantPath: "/courses", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).PostCourses(courses.NewPostCoursesParams(), learningTestAuth) return err }, }, { name: "update course", wantID: "updateCourses", wantMethod: "PUT", wantPath: "/courses", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).UpdateCourses(courses.NewUpdateCoursesParams(), learningTestAuth) return err }, }, { name: "list sections", wantID: "getCourseSections", wantMethod: "GET", wantPath: "/coursesections", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).GetCourseSections(courses.NewGetCourseSectionsParams(), learningTestAuth) return err }, }, { name: "create section", wantID: "postCourseSections", wantMethod: "POST", wantPath: "/coursesections", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).PostCourseSections(courses.NewPostCourseSectionsParams(), learningTestAuth) return err }, }, { name: "update section", wantID: "updateCourseSections", wantMethod: "PUT", wantPath: "/coursesections", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).UpdateCourseSections(courses.NewUpdateCourseSectionsParams(), learningTestAuth) return err }, }, { name: "list lessons", wantID: "getCourseLessons", wantMethod: "GET", wantPath: "/courselessons", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).GetCourseLessons(courses.NewGetCourseLessonsParams(), learningTestAuth) return err }, }, { name: "create lesson", wantID: "postCourseLessons", wantMethod: "POST", wantPath: "/courselessons", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).PostCourseLessons(courses.NewPostCourseLessonsParams(), learningTestAuth) return err }, }, { name: "update lesson", wantID: "updateCourseLessons", wantMethod: "PUT", wantPath: "/courselessons", call: func(transport openapiruntime.ContextualTransport) error { _, err := courses.New(transport, strfmt.Default).UpdateCourseLessons(courses.NewUpdateCourseLessonsParams(), learningTestAuth) return err }, }, { name: "list enrollments", wantID: "getEnrollments", wantMethod: "GET", wantPath: "/enrollments", call: func(transport openapiruntime.ContextualTransport) error { _, err := enrollments.New(transport, strfmt.Default).GetEnrollments(enrollments.NewGetEnrollmentsParams(), learningTestAuth) return err }, }, { name: "create enrollment", wantID: "postEnrollments", wantMethod: "POST", wantPath: "/enrollments", call: func(transport openapiruntime.ContextualTransport) error { _, err := enrollments.New(transport, strfmt.Default).PostEnrollments(enrollments.NewPostEnrollmentsParams(), learningTestAuth) return err }, }, { name: "update enrollment", wantID: "putEnrollments", wantMethod: "PUT", wantPath: "/enrollments", call: func(transport openapiruntime.ContextualTransport) error { _, err := enrollments.New(transport, strfmt.Default).PutEnrollments(enrollments.NewPutEnrollmentsParams(), learningTestAuth) return err }, }, { name: "list lesson progress", wantID: "getLessonProgresses", wantMethod: "GET", wantPath: "/lessonprogress", call: func(transport openapiruntime.ContextualTransport) error { _, err := lesson_progress.New(transport, strfmt.Default).GetLessonProgresses(lesson_progress.NewGetLessonProgressesParams(), learningTestAuth) return err }, }, { name: "create lesson progress", wantID: "postLessonProgresses", wantMethod: "POST", wantPath: "/lessonprogress", call: func(transport openapiruntime.ContextualTransport) error { _, err := lesson_progress.New(transport, strfmt.Default).PostLessonProgresses(lesson_progress.NewPostLessonProgressesParams(), learningTestAuth) return err }, }, { name: "update lesson progress", wantID: "putLessonProgresses", wantMethod: "PUT", wantPath: "/lessonprogress", call: func(transport openapiruntime.ContextualTransport) error { _, err := lesson_progress.New(transport, strfmt.Default).PutLessonProgresses(lesson_progress.NewPutLessonProgressesParams(), learningTestAuth) return err }, }, { name: "list issued certificates", wantID: "getIssuedCertificates", wantMethod: "GET", wantPath: "/issuedcertificates", call: func(transport openapiruntime.ContextualTransport) error { _, err := issued_certificates.New(transport, strfmt.Default).GetIssuedCertificates(issued_certificates.NewGetIssuedCertificatesParams(), learningTestAuth) return err }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { transport := &learningCaptureTransport{} 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 TestLearningSpecRequiresExactCompoundAuthentication(t *testing.T) { specText := readMembersLearningSpec(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{ "/courses": { "get": "getCourses", "post": "postCourses", "put": "updateCourses", }, "/coursesections": { "get": "getCourseSections", "post": "postCourseSections", "put": "updateCourseSections", }, "/courselessons": { "get": "getCourseLessons", "post": "postCourseLessons", "put": "updateCourseLessons", }, "/enrollments": { "get": "getEnrollments", "post": "postEnrollments", "put": "putEnrollments", }, "/lessonprogress": { "get": "getLessonProgresses", "post": "postLessonProgresses", "put": "putLessonProgresses", }, "/issuedcertificates": { "get": "getIssuedCertificates", }, } for path, methods := range operations { for method, operationID := range methods { operation := learningSpecOperationBlock(t, specText, path, method) if !strings.Contains(operation, " operationId: "+operationID+"\n") { t.Errorf("%s %s does not preserve operationId %q", strings.ToUpper(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", strings.ToUpper(method), path) } } } } func TestLearningUpdateClientsPreserveConflictResponses(t *testing.T) { conflicts := []interface{ IsCode(int) bool }{ courses.NewUpdateCoursesConflict(), courses.NewUpdateCourseSectionsConflict(), courses.NewUpdateCourseLessonsConflict(), enrollments.NewPutEnrollmentsConflict(), lesson_progress.NewPutLessonProgressesConflict(), } for _, conflict := range conflicts { if !conflict.IsCode(409) { t.Errorf("%T does not preserve the optimistic-conflict response", conflict) } } } func TestLearningGetOperationsPreserveIDFilters(t *testing.T) { recordID := "record-id" filters := []struct { name string got *string }{ {name: "Course", got: courses.NewGetCoursesParams().WithID(&recordID).ID}, {name: "CourseSection", got: courses.NewGetCourseSectionsParams().WithID(&recordID).ID}, {name: "CourseLesson", got: courses.NewGetCourseLessonsParams().WithID(&recordID).ID}, {name: "Enrollment", got: enrollments.NewGetEnrollmentsParams().WithID(&recordID).ID}, {name: "LessonProgress", got: lesson_progress.NewGetLessonProgressesParams().WithID(&recordID).ID}, {name: "IssuedCertificate", got: issued_certificates.NewGetIssuedCertificatesParams().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) } } } func TestLearningModelsMatchSanitizedProviderContract(t *testing.T) { assertExactModelFields(t, members_models.Course{}, map[string]string{ "CreatedByID": "*string", "CreatedDate": "*string", "Description": "*string", "Fulldescription": "*string", "ID": "string", "ImageAltText": "*string", "ImageURL": "*string", "InstructorID": "*string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "Logo": "*string", "Price": "*string", "Slug": "*string", "TemplateID": "*string", "Title": "*string", }) assertExactModelFields(t, members_models.CourseSection{}, map[string]string{ "Content": "*string", "CourseID": "*string", "CreatedByID": "*string", "CreatedDate": "*string", "ID": "string", "ImageAltText": "*string", "ImageURL": "*string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "Logo": "*string", "Order": "*int64", "Slug": "*string", "Title": "*string", }) assertExactModelFields(t, members_models.CourseLesson{}, map[string]string{ "Content": "*string", "CreatedByID": "*string", "CreatedDate": "*string", "ID": "string", "ImageAltText": "*string", "ImageURL": "*string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "Logo": "*string", "Order": "*int64", "SectionID": "*string", "Slug": "*string", "Title": "*string", "VideoURL": "*string", }) assertExactModelFields(t, members_models.Enrollment{}, map[string]string{ "Completed": "*bool", "CourseID": "*string", "CreatedByID": "*string", "CreatedDate": "*string", "EnrollmentDate": "*strfmt.Date", "ID": "string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "UserID": "*string", }) assertExactModelFields(t, members_models.LessonProgress{}, map[string]string{ "Completed": "bool", "CompletedAt": "*string", "CreatedByID": "*string", "CreatedDate": "*string", "EnrollmentID": "*string", "ID": "string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "LessonID": "*string", "TenantID": "*string", "UserID": "*string", }) assertExactModelFields(t, members_models.IssuedCertificate{}, map[string]string{ "AccountID": "*string", "CreatedByID": "*string", "CreatedDate": "*string", "EnrollmentID": "*string", "ExpirationDate": "*string", "ID": "string", "IssueDate": "*string", "LastModifiedByID": "*string", "LastModifiedDate": "*string", "TemplateID": "*string", "UserID": "*string", }) price := "125.50" if err := (&members_models.Course{Price: &price}).Validate(strfmt.Default); err != nil { t.Fatalf("valid exact-decimal Course.Price rejected: %v", err) } unsafePrice := "125.505" if err := (&members_models.Course{Price: &unsafePrice}).Validate(strfmt.Default); err == nil { t.Fatal("Course.Price accepted a value outside DECIMAL(10,2)") } body, err := json.Marshal(members_models.IssuedCertificate{ID: "issued-certificate-id"}) if err != nil { t.Fatalf("marshal IssuedCertificate: %v", err) } for _, forbidden := range []string{"VerificationCode", "CertificateID"} { if bytes.Contains(body, []byte(forbidden)) { t.Errorf("sanitized IssuedCertificate JSON exposes %s: %s", forbidden, body) } } } func TestLearningSingleRecordRequestsRejectBulkPayloads(t *testing.T) { tests := []struct { name string validate func() error }{ { name: "course", validate: func() error { return (&members_models.CourseRequest{Data: []*members_models.Course{{}, {}}}).Validate(strfmt.Default) }, }, { name: "course section", validate: func() error { return (&members_models.CourseSectionRequest{Data: []*members_models.CourseSection{{}, {}}}).Validate(strfmt.Default) }, }, { name: "course lesson", validate: func() error { return (&members_models.CourseLessonRequest{Data: []*members_models.CourseLesson{{}, {}}}).Validate(strfmt.Default) }, }, { name: "enrollment", validate: func() error { return (&members_models.EnrollmentRequest{Data: []*members_models.Enrollment{{}, {}}}).Validate(strfmt.Default) }, }, { name: "lesson progress", validate: func() error { return (&members_models.LessonProgressRequest{Data: []*members_models.LessonProgress{{}, {}}}).Validate(strfmt.Default) }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if err := test.validate(); err == nil { t.Fatal("generated single-record request accepted a bulk payload") } }) } } func TestLearningLifecycleBoundariesRemainVisible(t *testing.T) { specText := readMembersLearningSpec(t) expectations := []struct { path string method string terms []string }{ {path: "/coursesections", method: "put", terms: []string{"CourseID is immutable"}}, {path: "/courselessons", method: "put", terms: []string{"SectionID is immutable"}}, {path: "/enrollments", method: "put", terms: []string{"UserID and CourseID are immutable", "completion is monotonic"}}, {path: "/lessonprogress", method: "put", terms: []string{"TenantID, UserID, EnrollmentID, and LessonID are immutable", "completion is monotonic", "CompletedAt is server-owned"}}, {path: "/issuedcertificates", method: "get", terms: []string{"verification material is never returned", "Issuance, mutation, revocation, and deletion", "intentionally unavailable"}}, } for _, expectation := range expectations { block := learningSpecOperationBlock(t, specText, expectation.path, expectation.method) for _, term := range expectation.terms { if !strings.Contains(block, term) { t.Errorf("%s %s lost lifecycle boundary %q", strings.ToUpper(expectation.method), expectation.path, term) } } } } func TestLearningSurfaceOmitsUnsafeCertificateAndDeleteOperations(t *testing.T) { contracts := []struct { name string typ reflect.Type }{ {name: "Courses", typ: reflect.TypeOf((*courses.ClientService)(nil)).Elem()}, {name: "Enrollments", typ: reflect.TypeOf((*enrollments.ClientService)(nil)).Elem()}, {name: "LessonProgress", typ: reflect.TypeOf((*lesson_progress.ClientService)(nil)).Elem()}, {name: "IssuedCertificates", typ: reflect.TypeOf((*issued_certificates.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") { t.Errorf("%s client exposes unsupported delete operation %s", contract.name, method) } } } for _, method := range []string{ "PostIssuedCertificate", "PostIssuedCertificateContext", "PostIssuedCertificates", "PostIssuedCertificatesContext", "PutIssuedCertificate", "PutIssuedCertificateContext", "PutIssuedCertificates", "PutIssuedCertificatesContext", "DeleteIssuedCertificate", "DeleteIssuedCertificateContext", } { if _, exists := contracts[3].typ.MethodByName(method); exists { t.Errorf("read-only IssuedCertificate client exposes forbidden operation %s", method) } } repoRoot := learningRepoRoot(t) certificateClientDir := filepath.Join(repoRoot, "api", "members", "members_client", "certificates") if entries, err := os.ReadDir(certificateClientDir); err == nil { for _, entry := range entries { if strings.HasSuffix(entry.Name(), ".go") { t.Errorf("removed /certificates generated client still contains %s", entry.Name()) } } } else if !os.IsNotExist(err) { t.Fatalf("inspect removed certificate client directory: %v", err) } for _, removed := range []string{ filepath.Join(repoRoot, "api", "members", "members_models", "certificate.go"), filepath.Join(repoRoot, "api", "members", "members_models", "certificate_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "certificate_response.go"), filepath.Join(repoRoot, "api", "members", "members_models", "issued_certificate_request.go"), } { if _, err := os.Stat(removed); !os.IsNotExist(err) { t.Errorf("removed unsafe generated surface still exists: %s", removed) } } specText := readMembersLearningSpec(t) if strings.Contains(specText, "\n /certificates:") { t.Fatal("authoritative spec still exposes removed /certificates path") } issuedPath := learningSpecPathBlock(t, specText, "/issuedcertificates") for _, forbidden := range []string{" post:", " put:", " patch:", " delete:"} { if strings.Contains(issuedPath, forbidden) { t.Fatalf("authoritative IssuedCertificate contract exposes forbidden operation %q", strings.TrimSpace(forbidden)) } } } func TestMembersSpecIsPinnedToLearningProviderSource(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 != membersLearningSpecSHA256 { t.Fatalf("Members spec drifted from Learning merge fb25d140: SHA-256 = %s, want %s", got, membersLearningSpecSHA256) } 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 TestGeneratedLearningSurfaceHasNoSalesforceOrEmbeddedSecrets(t *testing.T) { repoRoot := learningRepoRoot(t) targets := []string{ filepath.Join(repoRoot, "api", "members", "members_client", "courses"), filepath.Join(repoRoot, "api", "members", "members_client", "enrollments"), filepath.Join(repoRoot, "api", "members", "members_client", "lesson_progress"), filepath.Join(repoRoot, "api", "members", "members_client", "issued_certificates"), filepath.Join(repoRoot, "api", "members", "members_models", "course.go"), filepath.Join(repoRoot, "api", "members", "members_models", "course_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "course_section.go"), filepath.Join(repoRoot, "api", "members", "members_models", "course_section_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "course_lesson.go"), filepath.Join(repoRoot, "api", "members", "members_models", "course_lesson_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "enrollment.go"), filepath.Join(repoRoot, "api", "members", "members_models", "enrollment_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "lesson_progress.go"), filepath.Join(repoRoot, "api", "members", "members_models", "lesson_progress_request.go"), filepath.Join(repoRoot, "api", "members", "members_models", "issued_certificate.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 Learning target %s: %v", target, err) } } } func assertExactModelFields(t *testing.T, model any, want map[string]string) { t.Helper() modelType := reflect.TypeOf(model) if modelType.NumField() != len(want) { t.Errorf("%s has %d fields, want exact authoritative set of %d", modelType.Name(), modelType.NumField(), len(want)) } for name, wantType := range want { field, exists := modelType.FieldByName(name) if !exists { t.Errorf("%s is missing authoritative field %s", modelType.Name(), name) continue } if field.Type.String() != wantType { t.Errorf("%s.%s type = %s, want %s", modelType.Name(), name, field.Type, wantType) } if got := strings.Split(field.Tag.Get("json"), ",")[0]; got != name { t.Errorf("%s.%s JSON name = %q, want %q", modelType.Name(), name, got, name) } } } func readMembersLearningSpec(t *testing.T) string { t.Helper() content, err := os.ReadFile(filepath.Join(learningRepoRoot(t), "swagger", "members-vernonkeenan.yaml")) if err != nil { t.Fatalf("read Members Swagger: %v", err) } return string(content) } func learningSpecPathBlock(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 learningSpecOperationBlock(t *testing.T, specText, path, method string) string { t.Helper() pathBlock := learningSpecPathBlock(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 learningRepoRoot(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), "..", "..", "..")) }