Skip to content

Conversation

@CodyWMitchell
Copy link
Contributor

@CodyWMitchell CodyWMitchell commented Nov 6, 2025

RHCLOUD-40633

Summary by Sourcery

Add fuzzy search endpoint for quickstarts by matching displayName using Levenshtein distance with configurable tolerance and pagination; enable database fuzzystrmatch extension; update tests and sample data accordingly.

New Features:

  • Introduce GET /quickstarts/fuzzy-search endpoint requiring q, and supporting max_distance, limit, and offset parameters
  • Return fuzzy search results including distance, search_term, and max_distance fields

Enhancements:

  • Adjust test quickstart content to include spec.displayName and related fields for realistic fuzzy matching

Deployment:

  • Enable fuzzystrmatch extension for Levenshtein distance in PostgreSQL during database initialization

Tests:

  • Add comprehensive TestFuzzySearch suite covering missing term validation, exact matches, fuzzy matches with typos, pagination, strict distance filtering, and displayName matching

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Nov 6, 2025

Reviewer's Guide

This PR implements a new fuzzy-search endpoint for quickstarts by leveraging PostgreSQL’s fuzzystrmatch Levenshtein function on the spec.displayName field, adds the necessary schema extension, and verifies behavior through extensive tests.

Sequence diagram for the new fuzzy search endpoint request flow

sequenceDiagram
    actor User
    participant API as "ServerAdapter (API)"
    participant DB as "Database"
    User->>API: GET /quickstarts/fuzzy-search?q=term&max_distance=3&limit=50&offset=0
    API->>DB: Execute Levenshtein fuzzy search query
    DB-->>API: Return matching quickstarts with distance
    API-->>User: Respond with JSON (data, search_term, max_distance)
Loading

Entity relationship diagram for fuzzy search result data structure

erDiagram
    QUICKSTART {
      uint ID
      string Name
      bytes Content
    }
    FUZZY_SEARCH_RESULT {
      int Distance
    }
    FUZZY_SEARCH_RESULT ||--|{ QUICKSTART : contains
Loading

Class diagram for FuzzySearchResult and related types

classDiagram
    class FuzzySearchResult {
      +Quickstart Quickstart
      +int Distance
    }
    class Quickstart {
      +BaseModel BaseModel
      +string Name
      +[]byte Content
    }
    class BaseModel {
      +uint ID
    }
    FuzzySearchResult --> Quickstart
    Quickstart --> BaseModel
Loading

File-Level Changes

Change Details Files
Implement fuzzy search endpoint with Levenshtein distance
  • Define FuzzySearchResult struct to include distance metric
  • Implement findQuickstartsByFuzzySearch with raw SQL query using levenshtein on content->'spec'->>'displayName'
  • Add GetQuickstartsFuzzySearch handler to parse query params, enforce max_distance and pagination, and return JSON
pkg/routes/quickstarts_handlers.go
Enable PostgreSQL fuzzystrmatch extension on database initialization
  • Execute CREATE EXTENSION IF NOT EXISTS fuzzystrmatch in non-test environments
pkg/database/db.go
Add tests and enhance fixtures for fuzzy search functionality
  • Extend test quickstart fixtures to include spec.displayName, descriptions, and tasks
  • Register /fuzzy-search route in test router setup
  • Add TestFuzzySearch suite covering missing params, exact and fuzzy matches, pagination, and error handling
pkg/routes/quickstarts_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • In GetQuickstartsFuzzySearch you’re encoding a generated.BadRequest for server-side errors—consider returning a dedicated InternalServerError response to match the 500 status.
  • Tests register the route as /fuzzy-search but the handler name implies /quickstarts/fuzzy-search—make sure the endpoint path matches your production router setup.
  • Instead of executing CREATE EXTENSION at runtime, consider managing the fuzzystrmatch extension through a database migration so it’s version-controlled and only applied once.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In GetQuickstartsFuzzySearch you’re encoding a generated.BadRequest for server-side errors—consider returning a dedicated InternalServerError response to match the 500 status.
- Tests register the route as /fuzzy-search but the handler name implies /quickstarts/fuzzy-search—make sure the endpoint path matches your production router setup.
- Instead of executing CREATE EXTENSION at runtime, consider managing the fuzzystrmatch extension through a database migration so it’s version-controlled and only applied once.

## Individual Comments

### Comment 1
<location> `pkg/routes/quickstarts_test.go:488-489` </location>
<code_context>
+		assert.GreaterOrEqual(t, len(payload.Data), 0)
+	})
+
+	t.Run("should respect pagination", func(t *testing.T) {
+		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configuration&limit=1", nil)
+		response := httptest.NewRecorder()
+		router.ServeHTTP(response, request)
+
+		// Skip test if PostgreSQL extension is not available
+		if response.Code == 500 {
+			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
+		}
+
+		var payload *fuzzySearchResponse
+		json.NewDecoder(response.Body).Decode(&payload)
+		assert.Equal(t, 200, response.Code)
+		assert.LessOrEqual(t, len(payload.Data), 1)
+	})
+
</code_context>

<issue_to_address>
**suggestion (testing):** Add a test for offset pagination.

Please add a test case for the offset parameter to ensure it returns the correct subset of results during pagination.

```suggestion
		assert.LessOrEqual(t, len(payload.Data), 1)
	})

	t.Run("should respect offset pagination", func(t *testing.T) {
		// First, get all results for the query
		requestAll, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configuration", nil)
		responseAll := httptest.NewRecorder()
		router.ServeHTTP(responseAll, requestAll)

		if responseAll.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payloadAll *fuzzySearchResponse
		json.NewDecoder(responseAll.Body).Decode(&payloadAll)
		assert.Equal(t, 200, responseAll.Code)
		if len(payloadAll.Data) < 2 {
			t.Skip("Not enough results to test offset pagination")
		}

		// Now, get the second result using offset=1&limit=1
		requestOffset, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configuration&limit=1&offset=1", nil)
		responseOffset := httptest.NewRecorder()
		router.ServeHTTP(responseOffset, requestOffset)

		if responseOffset.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payloadOffset *fuzzySearchResponse
		json.NewDecoder(responseOffset.Body).Decode(&payloadOffset)
		assert.Equal(t, 200, responseOffset.Code)
		assert.Equal(t, 1, len(payloadOffset.Data))
		assert.Equal(t, payloadAll.Data[1], payloadOffset.Data[0])
	})
```
</issue_to_address>

### Comment 2
<location> `pkg/routes/quickstarts_test.go:449-454` </location>
<code_context>
+		}
+	})
+
+	t.Run("should find fuzzy matches with typos", func(t *testing.T) {
+		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Geting Startd&max_distance=5", nil)
+		response := httptest.NewRecorder()
+		router.ServeHTTP(response, request)
+
+		// Skip test if PostgreSQL extension is not available
+		if response.Code == 500 {
+			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
+		}
+
+		var payload *fuzzySearchResponse
+		json.NewDecoder(response.Body).Decode(&payload)
+		assert.Equal(t, 200, response.Code)
+		assert.Equal(t, "Geting Startd", payload.SearchTerm)
+		assert.Equal(t, 5, payload.MaxDistance)
+		// Should find matches despite typos
+		assert.GreaterOrEqual(t, len(payload.Data), 0)
+	})
+
</code_context>

<issue_to_address>
**suggestion (testing):** Assert on the actual content of fuzzy matches for typo queries.

Consider updating the test to assert that the expected quickstart(s) are present in the results, such as by checking for specific displayName values. This will better validate the fuzzy matching logic.

```suggestion
		assert.Equal(t, 200, response.Code)
		assert.Equal(t, "Geting Startd", payload.SearchTerm)
		assert.Equal(t, 5, payload.MaxDistance)
		// Should find matches despite typos
		assert.GreaterOrEqual(t, len(payload.Data), 0)

		// Assert that the expected quickstart is present in the results
		found := false
		for _, result := range payload.Data {
			if result.DisplayName == "Getting Started with Applications" {
				found = true
				break
			}
		}
		assert.True(t, found, "Expected quickstart 'Getting Started with Applications' not found in fuzzy search results")
	})
```
</issue_to_address>

### Comment 3
<location> `pkg/routes/quickstarts_test.go:507-524` </location>
<code_context>
+		assert.Equal(t, 0, len(payload.Data))
+	})
+
+	t.Run("should find quickstarts by displayName with fuzzy matching", func(t *testing.T) {
+		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configure&max_distance=2", nil)
+		response := httptest.NewRecorder()
+		router.ServeHTTP(response, request)
+
+		// Skip test if PostgreSQL extension is not available
+		if response.Code == 500 {
+			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
+		}
+
+		var payload *fuzzySearchResponse
+		json.NewDecoder(response.Body).Decode(&payload)
+		assert.Equal(t, 200, response.Code)
+		assert.Equal(t, "Configure", payload.SearchTerm)
+		assert.Equal(t, 2, payload.MaxDistance)
+		// Should find quickstarts with "Configure" in displayName
+		assert.GreaterOrEqual(t, len(payload.Data), 0)
+	})
+}
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a test for fuzzy search with special characters or case insensitivity.

Tests for queries with special characters, mixed case, or whitespace differences are missing. Please add coverage for these edge cases to ensure the fuzzy search endpoint behaves as expected.

```suggestion
	t.Run("should find quickstarts by displayName with fuzzy matching", func(t *testing.T) {
		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configure&max_distance=2", nil)
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)

		// Skip test if PostgreSQL extension is not available
		if response.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payload *fuzzySearchResponse
		json.NewDecoder(response.Body).Decode(&payload)
		assert.Equal(t, 200, response.Code)
		assert.Equal(t, "Configure", payload.SearchTerm)
		assert.Equal(t, 2, payload.MaxDistance)
		// Should find quickstarts with "Configure" in displayName
		assert.GreaterOrEqual(t, len(payload.Data), 0)
	})

	t.Run("should handle fuzzy search with special characters", func(t *testing.T) {
		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Confi!gure&max_distance=2", nil)
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)

		if response.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payload *fuzzySearchResponse
		json.NewDecoder(response.Body).Decode(&payload)
		assert.Equal(t, 200, response.Code)
		assert.Equal(t, "Confi!gure", payload.SearchTerm)
		assert.Equal(t, 2, payload.MaxDistance)
		// Should still find results if fuzzy matching works with special characters
		assert.GreaterOrEqual(t, len(payload.Data), 0)
	})

	t.Run("should handle fuzzy search with mixed case", func(t *testing.T) {
		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=conFIgURe&max_distance=2", nil)
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)

		if response.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payload *fuzzySearchResponse
		json.NewDecoder(response.Body).Decode(&payload)
		assert.Equal(t, 200, response.Code)
		assert.Equal(t, "conFIgURe", payload.SearchTerm)
		assert.Equal(t, 2, payload.MaxDistance)
		// Should find results regardless of case
		assert.GreaterOrEqual(t, len(payload.Data), 0)
	})

	t.Run("should handle fuzzy search with whitespace differences", func(t *testing.T) {
		request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Con figure&max_distance=2", nil)
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)

		if response.Code == 500 {
			t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
		}

		var payload *fuzzySearchResponse
		json.NewDecoder(response.Body).Decode(&payload)
		assert.Equal(t, 200, response.Code)
		assert.Equal(t, "Con figure", payload.SearchTerm)
		assert.Equal(t, 2, payload.MaxDistance)
		// Should find results even with whitespace differences
		assert.GreaterOrEqual(t, len(payload.Data), 0)
	})
```
</issue_to_address>

### Comment 4
<location> `pkg/routes/quickstarts_handlers.go:123` </location>
<code_context>
+}
+
+// GetQuickstartsFuzzySearch handles GET /quickstarts/fuzzy-search
+func (s *ServerAdapter) GetQuickstartsFuzzySearch(w http.ResponseWriter, r *http.Request) {
+	searchTerm := r.URL.Query().Get("q")
+	if searchTerm == "" {
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring the handler by extracting query parsing, JSON writing, and SQL logic into helpers and a service layer to keep the handler concise.

```suggestion
Consider extracting all the low-level plumbing (query‐param parsing, JSON error/success writing, raw SQL→model mapping) into small helpers or moving the fuzzy-search SQL into your service layer. This will keep the handler at ~20 lines:

1) pkg/utils/http.go – parse ints & write JSON:
```go
// ParseIntQuery returns the integer value of ?key or def if missing/invalid.
func ParseIntQuery(r *http.Request, key string, def int) int {
    if v := r.URL.Query().Get(key); v != "" {
        if i, err := strconv.Atoi(v); err == nil {
            return i
        }
    }
    return def
}

// WriteJSON sets headers, status and encodes v to JSON.
func WriteJSON(w http.ResponseWriter, status int, v interface{}) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    json.NewEncoder(w).Encode(v)
}
```

2) pkg/services/quickstart.go – move raw SQL into service and use GORM’s builder:
```go
func (svc *QuickstartService) FuzzySearch(ctx context.Context,
    term string, maxDist int, p Pagination,
) ([]FuzzySearchResult, error) {
    var results []FuzzySearchResult
    err := svc.db.
        Model(&models.Quickstart{}).
        Select("quickstarts.*, levenshtein(content->'spec'->>'displayName', ?) AS distance", term).
        Where("levenshtein(content->'spec'->>'displayName', ?) <= ?", term, maxDist).
        Order("distance ASC, content->'spec'->>'displayName' ASC").
        Limit(p.Limit).Offset(p.Offset).
        Scan(&results).Error
    return results, err
}
```

3) routes/serveradapter.go – the handler becomes:
```go
func (s *ServerAdapter) GetQuickstartsFuzzySearch(w http.ResponseWriter, r *http.Request) {
    q := r.URL.Query().Get("q")
    if q == "" {
        utils.WriteJSON(w, http.StatusBadRequest, generated.BadRequest{Msg: ptr("missing 'q'")})
        return
    }

    pagination := Pagination{
        Limit:  utils.ParseIntQuery(r, "limit", 50),
        Offset: utils.ParseIntQuery(r, "offset", 0),
    }
    maxDist := utils.ParseIntQuery(r, "max_distance", 3)

    results, err := s.quickstartService.FuzzySearch(r.Context(), q, maxDist, pagination)
    if err != nil {
        utils.WriteJSON(w, http.StatusInternalServerError, generated.BadRequest{Msg: ptr(err.Error())})
        return
    }
    utils.WriteJSON(w, http.StatusOK, map[string]interface{}{
        "data":         results,
        "search_term":  q,
        "max_distance": maxDist,
    })
}
```

This preserves all functionality but collapses ~120 lines of boilerplate into focused helpers & service code.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +488 to +512
assert.LessOrEqual(t, len(payload.Data), 1)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add a test for offset pagination.

Please add a test case for the offset parameter to ensure it returns the correct subset of results during pagination.

Suggested change
assert.LessOrEqual(t, len(payload.Data), 1)
})
assert.LessOrEqual(t, len(payload.Data), 1)
})
t.Run("should respect offset pagination", func(t *testing.T) {
// First, get all results for the query
requestAll, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configuration", nil)
responseAll := httptest.NewRecorder()
router.ServeHTTP(responseAll, requestAll)
if responseAll.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payloadAll *fuzzySearchResponse
json.NewDecoder(responseAll.Body).Decode(&payloadAll)
assert.Equal(t, 200, responseAll.Code)
if len(payloadAll.Data) < 2 {
t.Skip("Not enough results to test offset pagination")
}
// Now, get the second result using offset=1&limit=1
requestOffset, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configuration&limit=1&offset=1", nil)
responseOffset := httptest.NewRecorder()
router.ServeHTTP(responseOffset, requestOffset)
if responseOffset.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payloadOffset *fuzzySearchResponse
json.NewDecoder(responseOffset.Body).Decode(&payloadOffset)
assert.Equal(t, 200, responseOffset.Code)
assert.Equal(t, 1, len(payloadOffset.Data))
assert.Equal(t, payloadAll.Data[1], payloadOffset.Data[0])
})

Comment on lines +449 to +477
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Geting Startd", payload.SearchTerm)
assert.Equal(t, 5, payload.MaxDistance)
// Should find matches despite typos
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Assert on the actual content of fuzzy matches for typo queries.

Consider updating the test to assert that the expected quickstart(s) are present in the results, such as by checking for specific displayName values. This will better validate the fuzzy matching logic.

Suggested change
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Geting Startd", payload.SearchTerm)
assert.Equal(t, 5, payload.MaxDistance)
// Should find matches despite typos
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Geting Startd", payload.SearchTerm)
assert.Equal(t, 5, payload.MaxDistance)
// Should find matches despite typos
assert.GreaterOrEqual(t, len(payload.Data), 0)
// Assert that the expected quickstart is present in the results
found := false
for _, result := range payload.Data {
if result.DisplayName == "Getting Started with Applications" {
found = true
break
}
}
assert.True(t, found, "Expected quickstart 'Getting Started with Applications' not found in fuzzy search results")
})

Comment on lines +507 to +547
t.Run("should find quickstarts by displayName with fuzzy matching", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configure&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)

// Skip test if PostgreSQL extension is not available
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}

var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Configure", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should find quickstarts with "Configure" in displayName
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a test for fuzzy search with special characters or case insensitivity.

Tests for queries with special characters, mixed case, or whitespace differences are missing. Please add coverage for these edge cases to ensure the fuzzy search endpoint behaves as expected.

Suggested change
t.Run("should find quickstarts by displayName with fuzzy matching", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configure&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
// Skip test if PostgreSQL extension is not available
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Configure", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should find quickstarts with "Configure" in displayName
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
t.Run("should find quickstarts by displayName with fuzzy matching", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Configure&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
// Skip test if PostgreSQL extension is not available
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Configure", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should find quickstarts with "Configure" in displayName
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
t.Run("should handle fuzzy search with special characters", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Confi!gure&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Confi!gure", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should still find results if fuzzy matching works with special characters
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
t.Run("should handle fuzzy search with mixed case", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=conFIgURe&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "conFIgURe", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should find results regardless of case
assert.GreaterOrEqual(t, len(payload.Data), 0)
})
t.Run("should handle fuzzy search with whitespace differences", func(t *testing.T) {
request, _ := http.NewRequest(http.MethodGet, "/fuzzy-search?q=Con figure&max_distance=2", nil)
response := httptest.NewRecorder()
router.ServeHTTP(response, request)
if response.Code == 500 {
t.Skip("Skipping fuzzy search test - PostgreSQL fuzzystrmatch extension not available")
}
var payload *fuzzySearchResponse
json.NewDecoder(response.Body).Decode(&payload)
assert.Equal(t, 200, response.Code)
assert.Equal(t, "Con figure", payload.SearchTerm)
assert.Equal(t, 2, payload.MaxDistance)
// Should find results even with whitespace differences
assert.GreaterOrEqual(t, len(payload.Data), 0)
})

@CodyWMitchell CodyWMitchell force-pushed the fuzzy-search-endpoint branch 5 times, most recently from dec74f9 to 09bf18f Compare November 10, 2025 15:03
Copy link
Collaborator

@Hyperkid123 Hyperkid123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this query:

http://localhost:8000/api/quickstarts/v1/quickstarts/fuzzy-search?q=Advanced Cluster Security

The Advanced Cluster Security is a partial of Learn about Red Hat Advanced Cluster Security display name used in the acs-getting-started-acscs doc and got no results.

I think the query and how its structured is expecting the whole string and measures distance on that, not on a sub string.

github.com/prometheus/client_golang v1.23.2
github.com/redhatinsights/app-common-go v1.6.8
github.com/redhatinsights/platform-go-middlewares/v2 v2.0.0
github.com/redhatinsights/platform-go-middlewares v1.0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these dependency downgrades? Did you ran into any issues?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants