-
Notifications
You must be signed in to change notification settings - Fork 0
fix: allows nullable source_id in taxonomy #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| package tests | ||
|
|
||
| import ( | ||
| "context" | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/google/uuid" | ||
| "github.com/jackc/pgx/v5/pgxpool" | ||
| "github.com/stretchr/testify/require" | ||
|
|
||
| "github.com/formbricks/hub/internal/config" | ||
| "github.com/formbricks/hub/internal/models" | ||
| "github.com/formbricks/hub/internal/repository" | ||
| "github.com/formbricks/hub/pkg/database" | ||
| ) | ||
|
|
||
| func setupTaxonomyNoSourceTestDB(t *testing.T) *pgxpool.Pool { | ||
| t.Helper() | ||
|
|
||
| databaseURL := os.Getenv("DATABASE_URL") | ||
| if databaseURL == "" { | ||
| databaseURL = defaultTestDatabaseURL | ||
| } | ||
|
|
||
| t.Setenv("DATABASE_URL", databaseURL) | ||
|
|
||
| cfg, err := config.Load() | ||
| require.NoError(t, err) | ||
|
|
||
| db, err := database.NewPostgresPool(context.Background(), cfg.Database.URL, | ||
| database.WithPoolConfig(cfg.Database.PoolConfig()), | ||
| ) | ||
| require.NoError(t, err) | ||
|
|
||
| t.Cleanup(db.Close) | ||
|
|
||
| return db | ||
| } | ||
|
|
||
| // TestTaxonomyNoSourceScope verifies that feedback records with a NULL/blank source_id | ||
| // participate in taxonomy through the canonical "no source" bucket (empty-string scope). | ||
| func TestTaxonomyNoSourceScope(t *testing.T) { | ||
| ctx := context.Background() | ||
| db := setupTaxonomyNoSourceTestDB(t) | ||
| repo := repository.NewTaxonomyRepository(db) | ||
|
|
||
| tenantID := "taxonomy-nosource-" + uuid.NewString() | ||
| sourceType := "formbricks" | ||
| fieldID := "feedback" | ||
|
|
||
| const embeddingModel = "text-embedding-test" | ||
|
|
||
| t.Cleanup(func() { | ||
| _, _ = db.Exec(ctx, `DELETE FROM taxonomy_runs WHERE tenant_id = $1`, tenantID) | ||
| _, _ = db.Exec(ctx, `DELETE FROM feedback_records WHERE tenant_id = $1`, tenantID) | ||
| }) | ||
|
|
||
| // Two feedback records with no attributed source: one NULL, one blank. | ||
| for _, srcID := range []any{nil, " "} { | ||
| _, err := db.Exec(ctx, ` | ||
| INSERT INTO feedback_records ( | ||
| source_type, source_id, field_id, field_label, field_type, | ||
| value_text, tenant_id, submission_id | ||
| ) | ||
| VALUES ($1, $2, $3, $4, $5::field_type_enum, $6, $7, $8)`, | ||
| sourceType, srcID, fieldID, "Feedback", "text", | ||
| "Login was confusing", tenantID, "submission-"+uuid.NewString(), | ||
| ) | ||
| require.NoError(t, err) | ||
| } | ||
|
|
||
| // Discovery surfaces the records as a single "no source" bucket with empty SourceID. | ||
| options, err := repo.ListFieldOptions(ctx, tenantID, embeddingModel) | ||
| require.NoError(t, err) | ||
|
|
||
| var noSource *models.TaxonomyFieldOption | ||
|
|
||
| for i := range options { | ||
| if options[i].SourceType == sourceType && options[i].FieldID == fieldID { | ||
| noSource = &options[i] | ||
| } | ||
| } | ||
|
|
||
| require.NotNil(t, noSource, "expected a discovered field option for the no-source bucket") | ||
| require.Empty(t, noSource.SourceID, "no-source bucket must expose an empty source_id") | ||
| require.Equal(t, 2, noSource.RecordCount, "NULL and blank source_id must collapse into one bucket") | ||
|
|
||
| // Counting the empty-source scope matches both NULL and blank feedback rows. | ||
| scope := models.TaxonomyScope{ | ||
| TenantID: tenantID, | ||
| SourceType: sourceType, | ||
| SourceID: "", | ||
| FieldID: fieldID, | ||
| } | ||
|
|
||
| recordCount, _, _, err := repo.CountScopeInput(ctx, scope, embeddingModel) | ||
| require.NoError(t, err) | ||
| require.Equal(t, 2, recordCount, "empty-source scope must null-safe match NULL/blank source rows") | ||
|
|
||
| // A taxonomy run can be created for the empty-source scope and is found by the | ||
| // in-progress guard (empty string is a valid, comparable key in taxonomy tables). | ||
| run, created, err := repo.CreateRunIfAvailable(ctx, repository.CreateTaxonomyRunParams{ | ||
| TaxonomyScope: scope, | ||
| RecordCount: recordCount, | ||
| EmbeddingCount: 0, | ||
| }) | ||
| require.NoError(t, err) | ||
| require.True(t, created) | ||
| require.Empty(t, run.SourceID) | ||
|
|
||
| _, createdAgain, err := repo.CreateRunIfAvailable(ctx, repository.CreateTaxonomyRunParams{ | ||
| TaxonomyScope: scope, | ||
| }) | ||
| require.NoError(t, err) | ||
| require.False(t, createdAgain, "a second run for the same empty-source scope must reuse the in-progress run") | ||
| } | ||
|
|
||
| // TestTaxonomyListRunsSourceIDTriState verifies the tri-state source_id filter on run | ||
| // history: nil returns every source, a pointer to "" scopes to the no-source bucket, and | ||
| // a pointer to a concrete value scopes to that source. | ||
| func TestTaxonomyListRunsSourceIDTriState(t *testing.T) { | ||
| ctx := context.Background() | ||
| db := setupTaxonomyNoSourceTestDB(t) | ||
| repo := repository.NewTaxonomyRepository(db) | ||
|
|
||
| tenantID := "taxonomy-listfilter-" + uuid.NewString() | ||
| sourceType := "formbricks" | ||
| fieldID := "feedback" | ||
| sourcedID := "survey-" + uuid.NewString() | ||
|
|
||
| t.Cleanup(func() { | ||
| _, _ = db.Exec(ctx, `DELETE FROM taxonomy_runs WHERE tenant_id = $1`, tenantID) | ||
| }) | ||
|
|
||
| // One run with no source ("") and one with a concrete source. | ||
| for _, sourceID := range []string{"", sourcedID} { | ||
| _, created, err := repo.CreateRunIfAvailable(ctx, repository.CreateTaxonomyRunParams{ | ||
| TaxonomyScope: models.TaxonomyScope{ | ||
| TenantID: tenantID, | ||
| SourceType: sourceType, | ||
| SourceID: sourceID, | ||
| FieldID: fieldID, | ||
| }, | ||
| }) | ||
| require.NoError(t, err) | ||
| require.True(t, created) | ||
| } | ||
|
|
||
| ptr := func(s string) *string { return &s } | ||
|
|
||
| // nil filter: both runs. | ||
| all, err := repo.ListRuns(ctx, models.ListTaxonomyRunsFilters{TenantID: tenantID}) | ||
| require.NoError(t, err) | ||
| require.Len(t, all, 2) | ||
|
|
||
| // pointer to "": only the no-source run. | ||
| noSource, err := repo.ListRuns(ctx, models.ListTaxonomyRunsFilters{TenantID: tenantID, SourceID: ptr("")}) | ||
| require.NoError(t, err) | ||
| require.Len(t, noSource, 1) | ||
| require.Empty(t, noSource[0].SourceID) | ||
|
|
||
| // pointer to a concrete value: only that sourced run. | ||
| sourced, err := repo.ListRuns(ctx, models.ListTaxonomyRunsFilters{TenantID: tenantID, SourceID: ptr(sourcedID)}) | ||
| require.NoError(t, err) | ||
| require.Len(t, sourced, 1) | ||
| require.Equal(t, sourcedID, sourced[0].SourceID) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.