### Helper Function for Test Client Setup in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Provides a helper function `setupTestClient` to create and initialize a `literature.Client` instance within Go tests. It ensures that the client is set up correctly, handling any potential errors during initialization. ```go func setupTestClient(t *testing.T) *literature.Client { client, err := literature.New() require.NoError(t, err) return client } ``` -------------------------------- ### Go Unit Test Example (Arrange-Act-Assert) Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Demonstrates a typical Go unit test following the Arrange-Act-Assert pattern. It includes setting up a client, performing an action, and asserting the results. ```go func TestSearchPubMed(t *testing.T) { // Arrange client, err := literature.New() require.NoError(t, err) query := "machine learning" // Act result, err := client.Search(query) // Assert assert.NoError(t, err) assert.NotNil(t, result) assert.NotEmpty(t, result.PMIDs) } ``` -------------------------------- ### Go Mock Usage Example with Testify Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Illustrates how to use testify/mock in Go for mocking dependencies. It defines a mock HTTP client and sets up expectations for method calls. ```go type MockHTTPClient struct { mock.Mock } func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) { args := m.Called(req) return args.Get(0).(*http.Response), args.Error(1) } func TestWithMockClient(t *testing.T) { mockClient := new(MockHTTPClient) client, err := literature.New( literature.WithHTTPClient(mockClient), ) require.NoError(t, err) // Set up expectations mockResponse := &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(`mock response`)), } mockClient.On("Do", mock.Anything).Return(mockResponse, nil) // Test execution result, err := client.Search("test query") // Assertions assert.NoError(t, err) mockClient.AssertExpectations(t) } ``` -------------------------------- ### Go Integration Test Example Source: https://github.com/dictybase/literature/blob/develop/TESTING.md An example of an integration test in Go that makes actual external API calls. It uses build tags to conditionally compile and run. ```go //go:build integration // +build integration func TestSearchPubMedIntegration(t *testing.T) { // Integration test that makes real API calls client, err := literature.New() require.NoError(t, err) result, err := client.Search("COVID-19") assert.NoError(t, err) assert.NotEmpty(t, result.PMIDs) } ``` -------------------------------- ### Go Table-Driven Test Example Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Shows how to implement table-driven tests in Go for testing multiple input scenarios efficiently. Each test case is defined in a struct. ```go func TestValidatePMID(t *testing.T) { tests := []struct { name string pmid string wantErr bool errType ErrorType }{ { name: "valid PMID", pmid: "12345678", wantErr: false, }, { name: "empty PMID", pmid: "", wantErr: true, errType: ErrorTypeInvalidInput, }, { name: "non-numeric PMID", pmid: "abc123", wantErr: true, errType: ErrorTypeInvalidInput, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidatePMID(tt.pmid) if tt.wantErr { assert.Error(t, err) if litErr, ok := err.(*Error); ok { assert.Equal(t, tt.errType, litErr.Type) } } else { assert.NoError(t, err) } }) } } ``` -------------------------------- ### Go Options Pattern Usage Examples Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates how to use the functional options pattern in Go to configure components. Examples show creating a client with default settings, configuring with specific options like timeout and retries, and setting a maximum number of connections. ```Go // Usage examples client1 := NewClient() // Uses all defaults client2 := NewClient( WithTimeout(60*time.Second), WithRetries(5), WithDebug(true), ) client3 := NewClient(WithMaxConnections(20)) ``` -------------------------------- ### Run Benchmarks Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes benchmark tests to measure the performance of different parts of the Go code. ```bash # Run benchmarks for performance testing go test -bench=. ./... ``` -------------------------------- ### Go Variable Naming and Scope Examples Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates Go variable naming conventions based on scope, from single letters for loop indices to descriptive names for variables used across longer function scopes. It also shows examples of function parameters and package-level variables. ```Go func processData() { // Short scope: single letter acceptable for i := 0; i < 10; i++ { fmt.Println(i) } // Medium scope: short but descriptive users := fetchUsers() for _, user := range users { processUser(user) } } func longRunningFunction() { // Long scope: highly descriptive names authenticatedUserRepository := NewUserRepository() configurationManager := NewConfigManager() emailNotificationService := NewEmailService() // These variables are used throughout the function for page := 1; page <= totalPages; page++ { paginatedUserResults := authenticatedUserRepository.GetUsersByPage(page) for _, individualUser := range paginatedUserResults { userEmailAddress := individualUser.Email notificationPreferences := configurationManager.GetPreferences(individualUser.ID) if notificationPreferences.EmailEnabled { emailNotificationService.Send(userEmailAddress, "Welcome!") } } } } // Function parameters and package-level variables: descriptive func CalculateMonthlySubscriptionRevenue(subscriptionDetails []Subscription, discountCalculator DiscountService) decimal.Decimal { totalMonthlyRevenue := decimal.Zero for _, subscription := range subscriptionDetails { monthlyAmount := subscription.MonthlyPrice applicableDiscount := discountCalculator.Calculate(subscription) finalAmount := monthlyAmount.Sub(applicableDiscount) totalMonthlyRevenue = totalMonthlyRevenue.Add(finalAmount) } return totalMonthlyRevenue } ``` -------------------------------- ### YAML: GitHub Actions CI for Go Project Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Configures a GitHub Actions workflow to run tests and generate code coverage for a Go project. It checks out the code, sets up Go, installs gotestsum, runs tests with race detection, generates coverage, and uploads it to Codecov. ```YAML name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-go@v3 with: go-version: '1.21' - name: Install gotestsum run: go install gotest.tools/gotestsum@latest - name: Run tests run: gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -race ./... - name: Generate coverage run: gotestsum -- -coverprofile=coverage.out ./... - name: Upload coverage uses: codecov/codecov-action@v3 with: file: ./coverage.out ``` -------------------------------- ### Descriptive Test Naming Examples in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Illustrates effective test naming conventions in Go, emphasizing descriptive names that clearly state the scenario being tested, including expected outcomes for valid inputs, invalid inputs, and error conditions. ```go // Good func TestClient_GetArticle_ValidPMID_ReturnsArticle(t *testing.T) {} func TestClient_GetArticle_InvalidPMID_ReturnsError(t *testing.T) {} func TestClient_GetArticle_NetworkError_ReturnsWrappedError(t *testing.T) {} // Avoid func TestGetArticle(t *testing.T) {} func TestGetArticleError(t *testing.T) {} ``` -------------------------------- ### Install dictybase/literature Go Package Source: https://github.com/dictybase/literature/blob/develop/README.md This command installs the dictybase/literature Go package, which is a client library for accessing scientific literature from PubMed and EuropePMC. It is a prerequisite for using the library in your Go projects. ```bash go get github.com/dictybase/literature ``` -------------------------------- ### Run Tests with Coverage Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes tests and includes coverage analysis using gotestsum. This helps in understanding the test coverage of the codebase. ```bash # Run tests with coverage gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -cover ./... ``` -------------------------------- ### View Coverage in Browser Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Opens the generated coverage report in a web browser, providing an interactive visualization of test coverage. ```bash # View coverage in browser go tool cover -html=coverage.out ``` -------------------------------- ### Run Tests with Verbose Output Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes tests with standard verbose output using gotestsum, which is helpful for debugging. It includes icon-based formatting. ```bash # Run with verbose output for debugging gotestsum --format-hide-empty-pkg --format standard-verbose --format-icons hivis ``` -------------------------------- ### Helper Function for Mock HTTP Client in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Creates a mock HTTP client in Go for testing purposes. This helper function configures the mock client to return a specified response body and status code for any `Do` method call. ```go func createMockHTTPClient(t *testing.T, response string, statusCode int) *MockHTTPClient { mockClient := &MockHTTPClient{} resp := &http.Response{ StatusCode: statusCode, Body: io.NopCloser(strings.NewReader(response)), } mockClient.On("Do", mock.Anything).Return(resp, nil) return mockClient } ``` -------------------------------- ### Generate Coverage Report Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Generates a coverage profile file (coverage.out) for the project's Go code. This file can be used to analyze test coverage. ```bash # Generate coverage report gotestsum -- -coverprofile=coverage.out ./... ``` -------------------------------- ### Go: Configure Literature Client Timeout Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Demonstrates how to configure a timeout for the literature client in Go. This is useful for handling network latency or slow responses during integration tests. ```Go // Increase timeout for integration tests client, err := literature.New( literature.WithTimeout(60*time.Second), ) ``` -------------------------------- ### Shell: Debug Go Tests with Verbose Output Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Runs Go tests with verbose output using gotestsum, providing detailed information for debugging. This includes options for standard verbose formatting and icons. ```Shell # Run with verbose output gotestsum --format-hide-empty-pkg --format standard-verbose --format-icons hivis ``` -------------------------------- ### Run Tests for Specific Package Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Runs tests specifically for the internal package using gotestsum, providing a focused test execution. ```bash # Run tests for specific package gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis ./internal/... ``` -------------------------------- ### Run All Tests with gotestsum Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes all tests using gotestsum with a clean, icon-based output format (testdox). This command is useful for a quick overview of test results. ```bash # Run all tests with clean output gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis ``` -------------------------------- ### Generate Go Test Coverage Report Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Provides bash commands to generate and view Go test coverage reports. It uses `gotestsum` to create a coverage profile and `go tool cover` to display the results in the browser or as a text summary. ```bash # Generate coverage report gotestsum -- -coverprofile=coverage.out ./... # View coverage in browser go tool cover -html=coverage.out # Check coverage percentage go tool cover -func=coverage.out | grep total: ``` -------------------------------- ### Run Tests with Race Detection Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Runs tests with race condition detection enabled using gotestsum. This is crucial for identifying concurrency issues in the code. ```bash # Run tests with race detection gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -race ./... ``` -------------------------------- ### Shell: Run Go Tests with Race Detector Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes Go tests with the race detector enabled using gotestsum. This helps identify data races that can occur in concurrent Go programs. ```Shell # Run with race detector gotestsum -- -race ./... ``` -------------------------------- ### Check Coverage Percentage Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Retrieves and displays the total test coverage percentage from the coverage profile. ```bash # Check coverage percentage go tool cover -func=coverage.out | grep total: ``` -------------------------------- ### Run Specific Test Pattern with gotestsum Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Runs tests that match a specific pattern (e.g., TestFindSimilar) within the project. It uses gotestsum for enhanced output formatting. ```bash # Run specific test pattern gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -run TestFindSimilar ./... ``` -------------------------------- ### Go Parameter Structs for Test Functions Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Illustrates the use of parameter structs in Go for passing multiple arguments to test helper functions, enhancing maintainability. It defines structs for creating tag properties and asserting GRPC errors, along with a usage example. ```Go // tagPropertyCreateParams holds the parameters for the // createServiceTagPropertyCreate function. type tagPropertyCreateParams struct { tag string value string createdBy string timestamp *time.Time } // assertGrpcErrorParams holds the parameters for the assertGrpcError function. type assertGrpcErrorParams struct { assert *require.Assertions err error expectedCode codes.Code expectedMsgSubstring string } // Usage example func createServiceTagPropertyCreate( params *tagPropertyCreateParams, ) *feature.TagPropertyCreate { tagCreate := &feature.TagPropertyCreate{ Tag: params.tag, Value: params.value, CreatedBy: params.createdBy, } if params.timestamp != nil { tagCreate.CreatedAt = timestamppb.New(*params.timestamp) } return tagCreate } ``` -------------------------------- ### Go Functional Programming Utility Usage Examples Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates the usage of generic functional programming utilities in Go, including transforming string slices to integers, filtering even numbers, finding a specific user, and applying a function with error handling to a slice of identifiers. ```Go // Helper functions for transformations func stringToInt(s string) int { n, _ := strconv.Atoi(s) return n } func isEven(n int) bool { return n%2 == 0 } func isBob(u string) bool { return u == "bob" } // Transform slice elements strings := []string{"1", "2", "3"} numbers := Map(strings, stringToInt) // Filter slice elements numbers := []int{1, 2, 3, 4, 5} evens := Filter(numbers, isEven) // Find first matching element users := []string{"alice", "bob", "charlie"} user, found := Find(users, isBob) // For complex operations with error handling func (c *Client) processArticleWithError(pmid string) (*Article, error) { article, err := c.GetArticle(pmid) if err != nil { return nil, fmt.Errorf("failed to process article %s: %w", pmid, err) } return article, nil } // Usage with MapWithError pmids := []string{"12345", "67890", "11111"} articles, err := MapWithError(pmids, c.processArticleWithError) ``` -------------------------------- ### Go Struct Validation with go-playground/validator Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates how to use the go-playground/validator library in Go for validating struct fields and individual parameters. Includes examples of basic validation, custom validation functions, and formatting validation errors. ```go import ( "fmt" "strings" "github.com/go-playground/validator/v10" ) // Struct with validation tags type CreateUserRequest struct { Name string `validate:"required,min=2,max=50" json:"name" Email string `validate:"required,email" json:"email" Age int `validate:"gte=18,lte=120" json:"age" Password string `validate:"required,min=8" json:"password" Role string `validate:"required,oneof=admin user guest" json:"role" Website string `validate:"omitempty,url" json:"website" } // Global validator instance (thread-safe singleton) var validate = validator.New() // Validate struct fields func CreateUser(req CreateUserRequest) error { if err := validate.Struct(req); err != nil { return fmt.Errorf("validation failed: %w", err) } // Process valid request return nil } // Validate individual parameters func UpdateUserEmail(userID string, newEmail string) error { if err := validate.Var(newEmail, "required,email"); err != nil { return fmt.Errorf("invalid email: %w", err) } if err := validate.Var(userID, "required,uuid"); err != nil { return fmt.Errorf("invalid user ID: %w", err) } // Process update return nil } // Complex validation with nested structs type Address struct { Street string `validate:"required,min=5" City string `validate:"required" Country string `validate:"required,iso3166_1_alpha2" ZipCode string `validate:"required,postcode_iso3166_alpha2=US" } type UserProfile struct { User CreateUserRequest `validate:"required" Address Address `validate:"required" Tags []string `validate:"dive,required,min=2" } // Custom validation function func validateBusinessEmail(fl validator.FieldLevel) bool { email := fl.Field().String() // Business emails should not be from common free providers blockedDomains := []string{"gmail.com", "yahoo.com", "hotmail.com"} for _, domain := range blockedDomains { if strings.HasSuffix(email, "@"+domain) { return false } } return true } // Register custom validator func init() { validate.RegisterValidation("business_email", validateBusinessEmail) } // Usage with custom validator type BusinessUser struct { Email string `validate:"required,email,business_email"` } // Helper function to format validation errors func FormatValidationError(err error) string { if validationErrors, ok := err.(validator.ValidationErrors); ok { var messages []string for _, e := range validationErrors { switch e.Tag() { case "required": messages = append(messages, fmt.Sprintf("%s is required", e.Field())) case "email": messages = append(messages, fmt.Sprintf("%s must be a valid email", e.Field())) case "min": messages = append(messages, fmt.Sprintf("%s must be at least %s characters", e.Field(), e.Param())) case "max": messages = append(messages, fmt.Sprintf("%s must be at most %s characters", e.Field(), e.Param())) default: messages = append(messages, fmt.Sprintf("%s failed validation: %s", e.Field(), e.Tag())) } } return strings.Join(messages, "; ") } return err.Error() } ``` -------------------------------- ### Go: Test Suite for SetTags Operation Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md This Go code outlines a test suite for the `SetTags` operation in the Dictybase literature project. It includes setup for the test client and context, and calls various test functions to cover success scenarios, edge cases, timestamp handling, and error conditions. ```Go func TestSetTags(t *testing.T) { t.Parallel() client, assert := setup(t) ctx := context.Background() params := &testParams{ t: t, ctx: ctx, client: client, assert: assert, } // Success scenarios testSetTagsSuccess(params) testSetTagsSingleTag(params) testSetTagsMultipleTags(params) testSetTagsReplaceExisting(params) // Edge cases testSetTagsEmptyRequest(params) // Timestamp handling testSetTagsDefaultTimestamps(params) testSetTagsProvidedTimestamps(params) // Error conditions testSetTagsNonExistentFeature(params) testSetTagsInvalidRequest(params) } ``` -------------------------------- ### Shell: Pre-commit Hook for Go Tests and Coverage Source: https://github.com/dictybase/literature/blob/develop/TESTING.md A shell script for a Git pre-commit hook that runs Go tests using gotestsum and checks code coverage. It aborts the commit if tests fail or coverage drops below 80%. ```Shell #!/bin/sh # .git/hooks/pre-commit # Run tests before commit gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis if [ $? -ne 0 ]; then echo "Tests failed. Commit aborted." exit 1 fi # Check coverage COVERAGE=$(go tool cover -func=coverage.out | grep total: | awk '{print $3}' | sed 's/%//') if (( $(echo "$COVERAGE < 80" | bc -l) )); then echo "Coverage is below 80%. Current: ${COVERAGE}%" exit 1 fi ``` -------------------------------- ### Project Structure Overview (Text) Source: https://github.com/dictybase/literature/blob/develop/README.md Provides a hierarchical view of the literature project's directory structure, detailing the purpose of key files and subdirectories like internal services, examples, and test data. ```Text literature/ ├── doc.go # Package documentation ├── literature.go # Main PubMed client interface ├── europepmc.go # EuropePMC client interface ├── types.go # PubMed data structures ├── europepmc_types.go # EuropePMC data structures ├── options.go # PubMed configuration options ├── europepmc_options.go # EuropePMC configuration options ├── errors.go # Error types and handling ├── adapters.go # Internal service adapters ├── internal/ # Private implementation details │ ├── pubmed_service.go # PubMed API client │ └── europepmc_service.go # EuropePMC API client ├── examples/ # Usage examples │ ├── basic/ # Basic PubMed examples │ ├── advanced/ # Advanced PubMed examples │ └── europepmc/ # EuropePMC examples ├── testdata/ # Test fixtures └── cmd/pubmed/ # CLI tool (optional) ``` -------------------------------- ### Benchmark MapWithError in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Benchmarks the performance of the `MapWithError` function. It prepares a slice of PMIDs and a simulated processing function, then measures the execution time of `MapWithError` over multiple iterations. ```go func BenchmarkMapWithError(b *testing.B) { pmids := make([]string, 100) for i := range pmids { pmids[i] = fmt.Sprintf("%d", i+1000000) } processFunc := func(pmid string) (*Article, error) { // Simulate processing time.Sleep(time.Microsecond) return &Article{}, nil } b.ResetTimer() for i := 0; i < b.N; i++ { _, _ = MapWithError(pmids, processFunc) } } ``` -------------------------------- ### Shell: Run Go Tests Multiple Times Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Runs Go tests multiple times using gotestsum to help identify and debug flaky tests. Running tests repeatedly can expose intermittent failures. ```Shell # Run tests multiple times gotestsum -- -count=10 ./... ``` -------------------------------- ### Independent Test Execution in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Demonstrates how to ensure test independence using `t.Run` for subtests in Go. Each subtest represents an independent test case, allowing for better organization and isolation of test logic. ```go func TestSearchFlow(t *testing.T) { // Each subtest is independent t.Run("valid_query_returns_results", func(t *testing.T) { client := setupTestClient(t) // Test implementation }) t.Run("empty_query_returns_error", func(t *testing.T) { client := setupTestClient(t) // Test implementation }) } func setupTestClient(t *testing.T) *literature.Client { client, err := literature.New() require.NoError(t, err) return client } ``` -------------------------------- ### Shell: Run Specific Failing Go Test Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Executes a specific failing Go test using gotestsum with the -run flag and verbose output. This allows focused debugging of individual test cases. ```Shell # Run specific failing test gotestsum -- -run TestSpecificFailingTest -v ``` -------------------------------- ### Load Test Data in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Loads test data from the 'testdata/' directory for use in Go tests. It reads a specified file and returns its content as a byte slice, asserting that no error occurred during file reading. ```go func loadTestData(t *testing.T, filename string) []byte { data, err := os.ReadFile(filepath.Join("testdata", filename)) require.NoError(t, err) return data } ``` -------------------------------- ### PDF Error Handling Example (Go) Source: https://github.com/dictybase/literature/blob/develop/README.md Demonstrates how to handle specific PDF-related errors using Go's errors.As and a switch statement to differentiate error types like article not found or download failures. ```Go err := pdfService.DownloadArticlePDF("12345", "test.pdf") if err != nil { var pdfErr *PDFError if errors.As(err, &pdfErr) { switch pdfErr.Type { case PDFErrorArticleNotFound: fmt.Println("Article not found") case PDFErrorPMCIDNotFound: fmt.Println("Article not available in PMC") case PDFErrorPDFNotAvailable: fmt.Println("PDF not available") case PDFErrorDownloadFailed: fmt.Printf("Download failed: %v\n", pdfErr.Err) } } } ``` -------------------------------- ### Parse Article XML Test in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Tests the `ParseArticleXML` function by loading sample XML data and asserting that the parsing process completes without errors and that the extracted article title matches the expected value. ```go func TestParseArticleXML(t *testing.T) { xmlData := loadTestData(t, "sample_article.xml") article, err := ParseArticleXML(xmlData) assert.NoError(t, err) assert.Equal(t, "Sample Title", article.Title) } ``` -------------------------------- ### Helper Function for Creating Test Articles in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md A helper function `createTestArticle` for generating mock `Article` objects in Go tests. It takes a PMID and returns a populated `Article` struct with predefined title, journal, publication year, and authors. ```go func createTestArticle(t *testing.T, pmid string) *Article { return &Article{ PMID: pmid, Title: "Test Article", Journal: "Test Journal", PubYear: "2023", Authors: []string{"Test Author"}, } } ``` -------------------------------- ### Go: Add Debug Output to Go Tests Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Shows how to add debug logging within Go tests using t.Logf. This method allows printing detailed information about test execution and variable states. ```Go func TestSomething(t *testing.T) { t.Logf("Debug info: %+v", someVariable) // Test implementation } ``` -------------------------------- ### Finding Similar Articles using EuropePMC Client Source: https://github.com/dictybase/literature/blob/develop/README.md Demonstrates how to find articles similar to a given PMID using the EuropePMC client. The example shows how to set a limit for the number of similar articles to retrieve and then iterates through the results, printing the title, publication year, PMID, and citation count for each similar article. ```Go client, err := literature.NewEuropePMCClient() if err != nil { log.Fatal(err) } // Find articles similar to a given PMID similarResult, err := client.FindSimilar( "25844567", literature.WithEuropePMCLimit(10), ) if err != nil { log.Fatal(err) } fmt.Printf("Found %d similar articles:\n", len(similarResult.Articles)) for _, article := range similarResult.Articles { fmt.Printf("- %s (%s)\n", article.Title, article.PubYear) fmt.Printf(" PMID: %s, Citations: %d\n", article.PMID, article.CitedByCount) } ``` -------------------------------- ### Test Search with Network Error in Go Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Tests the `Search` function's behavior when a network error occurs during the HTTP request. It uses a mock HTTP client to simulate the error and asserts that the `Search` function returns an error containing the expected network error message. ```go func TestSearchWithNetworkError(t *testing.T) { // Arrange mockClient := &MockHTTPClient{} client, err := literature.New( literature.WithHTTPClient(mockClient), ) require.NoError(t, err) networkErr := errors.New("network error") mockClient.On("Do", mock.Anything).Return((*http.Response)(nil), networkErr) // Act result, err := client.Search("test query") // Assert assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "network error") } ``` -------------------------------- ### Go Build, Test, and Lint Commands Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Provides commands for building, testing, and linting Go code. Includes running all tests, specific tests, verbose test output, code formatting, and linting the codebase. ```bash gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -run TestFindSimilar ./... gotestum --format-hide-empty-pkg --format standard-verbose --format-icons hivis gofumpt -w . golangcli-lint run ``` -------------------------------- ### Go GRPC Service Testing with bufconn Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Details how to perform in-memory GRPC service testing using `bufconn.Listen()` in Go for fast and isolated tests. It covers setting up the GRPC server and client, mocking dependencies, and ensuring resource cleanup. ```Go func setup( t *testing.T, ) (feature.FeatureAnnotationServiceClient, *require.Assertions) { t.Helper() assert := require.New(t) tra, err := testarango.NewTestArangoFromEnv(true) assert.NoError(err, "expect no error from creating an arangodb instance") // Create repository with isolated test collections repo, err := arangodb.NewFeatureAnnoRepo( arangodb.GetConnectParamsFromDB(tra), &arangodb.FeatureCollectionParams{ Feature: "feature_test", Pub: "pub_test", Edge: "feature_pub_test", Graph: "feature_pub_graph_test", }, ) assert.NoError(err) // Create service with mock dependencies svc, err := NewFeatureAnnotationService(&FeatureParams{ Repository: repo, Publisher: &MockMessage{}, // Mock message publisher }) assert.NoError(err) // GRPC server setup with bufconn server := grpc.NewServer() feature.RegisterFeatureAnnotationServiceServer(server, svc) lis := bufconn.Listen(1024 * 1024) go func() { if err = server.Serve(lis); err != nil { t.Logf("Server exited with error: %v", err) os.Exit(1) } }() // GRPC client setup dialer := func(context.Context, string) (net.Conn, error) { conn, errd := lis.Dial() assert.NoError(errd, "expect no error from creating listener") return conn, nil } conn, err := grpc.NewClient( "bufnet", grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithContextDialer(dialer), ) assert.NoError(err) // Cleanup resources t.Cleanup(func() { _ = repo.Dbh().Drop() conn.Close() lis.Close() server.Stop() }) return feature.NewFeatureAnnotationServiceClient(conn), assert } // Mock implementation for message publisher type MockMessage struct{} func (msn *MockMessage) Publish( subject string, feat *feature.FeatureAnnotation, ) { } ``` -------------------------------- ### Go User Creation with go-playground/validator Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates creating a user with validation using the `go-playground/validator` library. It first validates the request struct and then applies additional business logic validation, returning custom error types for different failure scenarios. ```Go // Validation errors using go-playground/validator func CreateUserWithValidation(req CreateUserRequest) error { // First validate the struct if err := validate.Struct(req); err != nil { if validationErrors, ok := err.(validator.ValidationErrors); ok { return fmt.Errorf("validation failed: %w", &UserValidationError{ Errors: validationErrors, }) } return fmt.Errorf("validation failed: %w", err) } // Additional business logic validation if strings.Contains(req.Email, "test") { return fmt.Errorf("test emails not allowed: %w", &BusinessLogicError{ Field: "email", Value: req.Email, Message: "production environment does not accept test emails", }) } // Process valid request return nil } ``` -------------------------------- ### Build and Format Commands (Bash) Source: https://github.com/dictybase/literature/blob/develop/README.md Lists essential bash commands for maintaining code quality, including formatting with `gofumpt` and linting with `golangcli-lint`, as well as building the project. ```Bash # Format code gofumpt -w . # Lint codebase golangcli-lint run # Build go build ``` -------------------------------- ### Go Error Handling with Type Assertions (errors.As) Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Illustrates how to handle different types of errors returned from `CreateUserWithValidation` using `errors.As`. It checks for `UserValidationError` and `BusinessLogicError` to log specific messages and suggests appropriate HTTP status codes for each error type. ```Go // Error checking with type assertions func HandleUserCreation(req CreateUserRequest) { err := CreateUserWithValidation(req) if err != nil { var validationErr *UserValidationError var businessErr *BusinessLogicError switch { case errors.As(err, &validationErr): log.Printf("Validation errors: %s", validationErr.Error()) // Handle validation errors - return 400 Bad Request case errors.As(err, &businessErr): log.Printf("Business logic error: %s", businessErr.Error()) // Handle business logic errors - return 422 Unprocessable Entity default: log.Printf("Unknown error: %s", err.Error()) // Handle unknown errors - return 500 Internal Server Error } } } ``` -------------------------------- ### Go: Test Empty PMID Validation Source: https://github.com/dictybase/literature/blob/develop/TESTING.md Tests the validation logic for an empty PMID, asserting the correct error type, message, and code. This function is part of the error message testing suite. ```Go func TestValidateInput_EmptyPMID_ReturnsSpecificError(t *testing.T) { err := ValidatePMID("") // Test error type var validationErr *ValidationError assert.True(t, errors.As(err, &validationErr)) // Test error message assert.Contains(t, err.Error(), "PMID cannot be empty") // Test error code if applicable assert.Equal(t, "EMPTY_PMID", validationErr.Code) } ``` -------------------------------- ### Configure PubMed Client Source: https://github.com/dictybase/literature/blob/develop/README.md Initializes a new PubMed client with customizable options such as timeout, user agent, and an HTTP client. ```Go client, err := literature.New( literature.WithTimeout(60*time.Second), literature.WithUserAgent("MyApp/1.0"), literature.WithHTTPClient(customHTTPClient), ) ``` -------------------------------- ### Go Options Pattern for Configurable Components Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Implements the functional options pattern in Go for creating configurable components. This pattern allows for flexible initialization of structs by accepting a variable number of option functions, providing default values, and overriding them as needed. ```Go // Option type for functional options type Option func(*Config) // Config struct holds the configuration type Config struct { timeout time.Duration retries int debug bool maxConnections int } // Option functions func WithTimeout(timeout time.Duration) Option { return func(c *Config) { c.timeout = timeout } } func WithRetries(retries int) Option { return func(c *Config) { c.retries = retries } } func WithDebug(debug bool) Option { return func(c *Config) { c.debug = debug } } func WithMaxConnections(max int) Option { return func(c *Config) { c.maxConnections = max } } // Constructor with default values and options func NewClient(opts ...Option) *Client { cfg := &Config{ timeout: 30 * time.Second, retries: 3, debug: false, maxConnections: 10, } for _, opt := range opts { opt(cfg) } return &Client{config: cfg} } ``` -------------------------------- ### Go Error Handling Best Practices Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Illustrates preferred Go error handling techniques, including always checking errors, returning meaningful wrapped errors with context using `fmt.Errorf`, and preserving error chains across multiple operations. ```Go import ( "fmt" "io" "os" ) // Avoid: Ignoring errors func badExample() { file, _ := os.Open("config.txt") data, _ := io.ReadAll(file) fmt.Println(string(data)) } // Preferred: Always check and wrap errors with context func goodExample() error { file, err := os.Open("config.txt") if err != nil { return fmt.Errorf("failed to open config file: %w", err) } defer file.Close() data, err := io.ReadAll(file) if err != nil { return fmt.Errorf("failed to read config file: %w", err) } fmt.Println(string(data)) return nil } // Multiple operations: preserve error chain func processUserData(userID string) error { user, err := fetchUser(userID) if err != nil { return fmt.Errorf("failed to fetch user %s: %w", userID, err) } profile, err := loadProfile(user.ProfileID) if err != nil { return fmt.Errorf("failed to load profile for user %s: %w", userID, err) } ``` -------------------------------- ### Testing Commands (Bash) Source: https://github.com/dictybase/literature/blob/develop/README.md Provides common bash commands for running tests using `gotestsum`, including options for running all tests, specific tests, and enabling verbose output. ```Bash # Run all tests gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis # Run specific test gotestsum --format-hide-empty-pkg --format testdox --format-icons hivis -- -run TestFetchArticle ./... # Run with verbose output gotestsum --format-hide-empty-pkg --format standard-verbose --format-icons hivis ``` -------------------------------- ### Configure EuropePMC Client Source: https://github.com/dictybase/literature/blob/develop/README.md Initializes a new EuropePMC client with specific configuration options including timeout, user agent, email, retry policy, and rate limiting. ```Go client, err := literature.NewEuropePMCClient( literature.WithEuropePMCTimeout(60*time.Second), literature.WithEuropePMCUserAgent("MyApp/1.0"), literature.WithEuropePMCEmail("contact@myapp.com"), literature.WithEuropePMCRetryPolicy(5, 2*time.Second), literature.WithEuropePMCRateLimit(5.0), // 5 requests per second ) ``` -------------------------------- ### Go Parameter Structs for Function Clarity Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Illustrates the best practice in Go of using structs to group parameters for functions that would otherwise have too many arguments. This improves code readability and maintainability. ```go // Avoid: Too many parameters func CreateUser(name, email, phone, address string, age int, active bool) error { // implementation } // Preferred: Use a struct for parameters type CreateUserParams struct { Name string Email string Phone string Address string Age int Active bool } func CreateUser(params CreateUserParams) error { // implementation } // Usage err := CreateUser(CreateUserParams{ Name: "John Doe", Email: "john@example.com", Phone: "555-0123", Address: "123 Main St", Age: 30, ``` -------------------------------- ### Functional Programming in Go Tests Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates using Go's collection utilities and the 'slices' package for data transformations, filtering, sorting, and searching within test code. Emphasizes functional approaches for improved readability and maintainability. ```Go params.assert.ElementsMatch( collection.Map( req.Attributes.Properties, extractTagAndValue, ), collection.Map( resp.Attributes.Properties, extractTagAndValue, ), "should have matching properties", ) // Use slices.SortFunc for consistent ordering slices.SortFunc( req.Attributes.Properties, sortTagPropertiesByTag, ) slices.SortFunc( resp.Attributes.Properties, sortTagPropertiesByTag, ) // Use slices.ContainsFunc for complex element search found := slices.ContainsFunc(result.Attributes.Properties, func(prop *feature.TagProperty) bool { return prop.Tag == expectedTag.Tag && prop.Value == expectedTag.Value && prop.CreatedBy == expectedTag.CreatedBy }) // Helper functions for data extraction func sortTagPropertiesByTag(a, b *feature.TagProperty) int { return strings.Compare( strings.ToLower(a.Tag), strings.ToLower(b.Tag), ) } func extractTagAndValue(prop *feature.TagProperty) *feature.TagProperty { return &feature.TagProperty{ Tag: prop.Tag, Value: prop.Value, } } ``` -------------------------------- ### Go Test Organization and Parallel Execution Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Demonstrates organizing Go tests into separate files and using `t.Parallel()` for concurrent execution of independent tests to improve performance. It includes a test function for creating feature annotations. ```Go func TestCreateFeatureAnnotation(t *testing.T) { t.Parallel() client, assert := setup(t) ctx := context.Background() params := &testParams{ t: t, ctx: ctx, client: client, assert: assert, } testCreateValidFeature(params) testCreateMissingFields(params) testCreateDuplicateFeature(params) } ``` -------------------------------- ### Timestamp and Time Handling in Go Source: https://github.com/dictybase/literature/blob/develop/CLAUDE.md Details strategies for handling timestamps in Go tests, including using `assert.WithinDuration` for tolerance, truncating timestamps for precision, and testing both auto-generated and user-provided timestamps. ```Go // Verify auto-generated timestamps are recent params.assert.WithinDuration( time.Now(), result.Attributes.Properties[idx].CreatedAt.AsTime(), 5*time.Second, "CreatedAt should be recent for tag %s", expectedTag.Tag, ) // Verify provided timestamps are preserved with truncation params.assert.Equal( expectedTimestamps[idx].Truncate(time.Second), result.Attributes.Properties[idx].CreatedAt.AsTime(). Truncate(time.Second), "CreatedAt should match provided timestamp for tag %s", expectedTag.Tag, ) // Helper function for timestamp behavior testing func testTimestampBehavior( params *testParams, featureID string, withProvidedTimestamps bool, operation func(string, []*feature.TagPropertyCreate) (*feature.FeatureAnnotation, error), ) { params.t.Helper() var newTags []*feature.TagPropertyCreate var expectedTimestamps []time.Time if withProvidedTimestamps { ```