-
Notifications
You must be signed in to change notification settings - Fork 56
feat(mantis): add schema and models #1417
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
Draft
StefanFVogel
wants to merge
1
commit into
kdlbs:main
Choose a base branch
from
StefanFVogel:feature/t01-mantis-schema-mo-mni
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
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
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||||||||||||||||||||||||||||
| package main | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||
| "testing" | ||||||||||||||||||||||||||||||||
| "time" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| "github.com/google/uuid" | ||||||||||||||||||||||||||||||||
| "github.com/jmoiron/sqlx" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| "github.com/kandev/kandev/internal/db" | ||||||||||||||||||||||||||||||||
| sqliterepo "github.com/kandev/kandev/internal/task/repository/sqlite" | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // TestDeleteMantisStateForReset_OnlyTargetWorkspace locks in the workspace | ||||||||||||||||||||||||||||||||
| // isolation contract: the cleanup deletes Mantis state for one workspace and | ||||||||||||||||||||||||||||||||
| // leaves every other workspace untouched. Catches accidental over-broad | ||||||||||||||||||||||||||||||||
| // DELETEs (e.g. dropping the WHERE clause) before they leak into specs where | ||||||||||||||||||||||||||||||||
| // two workspaces share a Playwright worker's database. | ||||||||||||||||||||||||||||||||
| func TestDeleteMantisStateForReset_OnlyTargetWorkspace(t *testing.T) { | ||||||||||||||||||||||||||||||||
| repo, sqlxDB := newE2EResetTestRepo(t) | ||||||||||||||||||||||||||||||||
| _ = repo | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| target := uuid.New().String() | ||||||||||||||||||||||||||||||||
| bystander := uuid.New().String() | ||||||||||||||||||||||||||||||||
| seedMantisFixtures(t, sqlxDB, target) | ||||||||||||||||||||||||||||||||
| seedMantisFixtures(t, sqlxDB, bystander) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if err := deleteMantisStateForReset(context.Background(), sqlxDB.DB, target); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("deleteMantisStateForReset: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if got := countMantisConfigs(t, sqlxDB, target); got != 0 { | ||||||||||||||||||||||||||||||||
| t.Errorf("target mantis_configs still has %d rows, want 0", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if got := countMantisWatches(t, sqlxDB, target); got != 0 { | ||||||||||||||||||||||||||||||||
| t.Errorf("target mantis_issue_watches still has %d rows, want 0", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if got := countMantisWatchTasks(t, sqlxDB, target); got != 0 { | ||||||||||||||||||||||||||||||||
| t.Errorf("target mantis_issue_watch_tasks still has %d rows, want 0", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if got := countMantisConfigs(t, sqlxDB, bystander); got != 1 { | ||||||||||||||||||||||||||||||||
| t.Errorf("bystander mantis_configs has %d rows, want 1 (cleanup leaked across workspaces)", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if got := countMantisWatches(t, sqlxDB, bystander); got != 1 { | ||||||||||||||||||||||||||||||||
| t.Errorf("bystander mantis_issue_watches has %d rows, want 1", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if got := countMantisWatchTasks(t, sqlxDB, bystander); got != 1 { | ||||||||||||||||||||||||||||||||
| t.Errorf("bystander mantis_issue_watch_tasks has %d rows, want 1", got) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // TestDeleteMantisStateForReset_Idempotent locks in the no-op-safe contract: | ||||||||||||||||||||||||||||||||
| // running the cleanup against a workspace that has no Mantis rows must not | ||||||||||||||||||||||||||||||||
| // error. Reset endpoints are hit between every spec, so the empty-state path | ||||||||||||||||||||||||||||||||
| // is hot. | ||||||||||||||||||||||||||||||||
| func TestDeleteMantisStateForReset_Idempotent(t *testing.T) { | ||||||||||||||||||||||||||||||||
| _, sqlxDB := newE2EResetTestRepo(t) | ||||||||||||||||||||||||||||||||
| ws := uuid.New().String() | ||||||||||||||||||||||||||||||||
| if err := deleteMantisStateForReset(context.Background(), sqlxDB.DB, ws); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("first call: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if err := deleteMantisStateForReset(context.Background(), sqlxDB.DB, ws); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("second call: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // newE2EResetTestRepo opens an on-disk SQLite repo so initSchema runs through | ||||||||||||||||||||||||||||||||
| // the same code path the live binary uses on boot. | ||||||||||||||||||||||||||||||||
| func newE2EResetTestRepo(t *testing.T) (*sqliterepo.Repository, *sqlx.DB) { | ||||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||||
| tmpDir := t.TempDir() | ||||||||||||||||||||||||||||||||
| dbConn, err := db.OpenSQLite(filepath.Join(tmpDir, "test.db")) | ||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("open sqlite: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | ||||||||||||||||||||||||||||||||
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | ||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("new repo: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| t.Cleanup(func() { _ = sqlxDB.Close() }) | ||||||||||||||||||||||||||||||||
| return repo, sqlxDB | ||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+85
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: Register Prompt for AI agents
Suggested change
|
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // seedMantisFixtures writes one row into each of the three Mantis tables for | ||||||||||||||||||||||||||||||||
| // the given workspace, with a deterministic watch ID chain so the watch_tasks | ||||||||||||||||||||||||||||||||
| // row's FK to mantis_issue_watches is satisfied. | ||||||||||||||||||||||||||||||||
| func seedMantisFixtures(t *testing.T, db *sqlx.DB, workspaceID string) { | ||||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||||
| now := time.Now().UTC() | ||||||||||||||||||||||||||||||||
| if _, err := db.Exec(` | ||||||||||||||||||||||||||||||||
| INSERT INTO mantis_configs ( | ||||||||||||||||||||||||||||||||
| workspace_id, base_url, username, auth_method, default_project_id, | ||||||||||||||||||||||||||||||||
| last_ok, last_error, created_at, updated_at | ||||||||||||||||||||||||||||||||
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||||||||||||||||||||||||||||||||
| workspaceID, "https://example.mantis", "user", "api_token", 0, 0, "", now, now, | ||||||||||||||||||||||||||||||||
| ); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("seed mantis_configs: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| watchID := uuid.New().String() | ||||||||||||||||||||||||||||||||
| if _, err := db.Exec(` | ||||||||||||||||||||||||||||||||
| INSERT INTO mantis_issue_watches ( | ||||||||||||||||||||||||||||||||
| id, workspace_id, workflow_id, workflow_step_id, filter, | ||||||||||||||||||||||||||||||||
| agent_profile_id, executor_profile_id, prompt, enabled, | ||||||||||||||||||||||||||||||||
| poll_interval_seconds, last_error, created_at, updated_at | ||||||||||||||||||||||||||||||||
| ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, | ||||||||||||||||||||||||||||||||
| watchID, workspaceID, "wf", "step", "{}", "", "", "", 1, 300, "", now, now, | ||||||||||||||||||||||||||||||||
| ); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("seed mantis_issue_watches: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| if _, err := db.Exec(` | ||||||||||||||||||||||||||||||||
| INSERT INTO mantis_issue_watch_tasks ( | ||||||||||||||||||||||||||||||||
| id, issue_watch_id, issue_id, issue_url, task_id, created_at | ||||||||||||||||||||||||||||||||
| ) VALUES (?, ?, ?, ?, ?, ?)`, | ||||||||||||||||||||||||||||||||
| uuid.New().String(), watchID, "42", "https://example.mantis/view.php?id=42", "", now, | ||||||||||||||||||||||||||||||||
| ); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("seed mantis_issue_watch_tasks: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| func countMantisConfigs(t *testing.T, db *sqlx.DB, workspaceID string) int { | ||||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||||
| var n int | ||||||||||||||||||||||||||||||||
| if err := db.Get(&n, `SELECT COUNT(*) FROM mantis_configs WHERE workspace_id = ?`, workspaceID); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("count mantis_configs: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| return n | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| func countMantisWatches(t *testing.T, db *sqlx.DB, workspaceID string) int { | ||||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||||
| var n int | ||||||||||||||||||||||||||||||||
| if err := db.Get(&n, `SELECT COUNT(*) FROM mantis_issue_watches WHERE workspace_id = ?`, workspaceID); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("count mantis_issue_watches: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| return n | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| func countMantisWatchTasks(t *testing.T, db *sqlx.DB, workspaceID string) int { | ||||||||||||||||||||||||||||||||
| t.Helper() | ||||||||||||||||||||||||||||||||
| var n int | ||||||||||||||||||||||||||||||||
| if err := db.Get(&n, ` | ||||||||||||||||||||||||||||||||
| SELECT COUNT(*) FROM mantis_issue_watch_tasks | ||||||||||||||||||||||||||||||||
| WHERE issue_watch_id IN (SELECT id FROM mantis_issue_watches WHERE workspace_id = ?) | ||||||||||||||||||||||||||||||||
| `, workspaceID); err != nil { | ||||||||||||||||||||||||||||||||
| t.Fatalf("count mantis_issue_watch_tasks: %v", err) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| return n | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move cleanup registration above
NewWithDBfatal path.If
NewWithDB(...)fails,t.Fatalfexits before cleanup is registered, leaving the DB open on that path.Suggested fix
As per coding guidelines, “Register
t.Cleanupimmediately after creating resources that need teardown ... before anyt.Fatal/t.Fatalfpath to prevent resource leaks.”📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines