Files
kandev/apps/backend/internal/gitlab/store_e2e_reset.go
T
2026-09-05 15:30:20 +00:00

104 lines
3.5 KiB
Go

package gitlab
import "context"
// E2EResetResult summarizes the workspace-scoped poller definitions removed
// before the shared E2E task reset starts deleting tasks.
type E2EResetResult struct {
ReviewWatches int `json:"review_watches"`
IssueWatches int `json:"issue_watches"`
}
// ResetWorkspaceE2E removes all workspace-owned GitLab state except the
// connection. The service deletes the connection separately so its secret can
// be removed through the credential store instead of bypassing it with SQL.
func (s *Store) ResetWorkspaceE2E(ctx context.Context, workspaceID string) (E2EResetResult, error) {
tx, err := s.db.BeginTxx(ctx, nil)
if err != nil {
return E2EResetResult{}, err
}
defer func() { _ = tx.Rollback() }()
queries := []string{
`DELETE FROM gitlab_review_mr_tasks WHERE review_watch_id IN
(SELECT id FROM gitlab_review_watches WHERE workspace_id = ?)`,
`DELETE FROM gitlab_issue_watch_tasks WHERE issue_watch_id IN
(SELECT id FROM gitlab_issue_watches WHERE workspace_id = ?)`,
}
for _, query := range queries {
if _, err := tx.ExecContext(ctx, tx.Rebind(query), workspaceID); err != nil {
return E2EResetResult{}, err
}
}
// gitlab_task_mr_state / gitlab_task_mr_options are deleted before the
// task rows below, per the E2E reset invariant in apps/backend/AGENTS.md:
// any workspace-scoped state a global poller reads (ListAutomationSubscribedTaskMRs)
// must be gone before task deletion, or the poller can recreate rows
// mid-reset and later tests see duplicates.
if err := s.deleteMRAutomationForWorkspace(ctx, tx, workspaceID); err != nil {
return E2EResetResult{}, err
}
reviewResult, err := tx.ExecContext(ctx, tx.Rebind(
`DELETE FROM gitlab_review_watches WHERE workspace_id = ?`), workspaceID)
if err != nil {
return E2EResetResult{}, err
}
issueResult, err := tx.ExecContext(ctx, tx.Rebind(
`DELETE FROM gitlab_issue_watches WHERE workspace_id = ?`), workspaceID)
if err != nil {
return E2EResetResult{}, err
}
workspaceDeletes := []string{
`DELETE FROM gitlab_action_presets WHERE workspace_id = ?`,
`DELETE FROM gitlab_mr_watches WHERE task_id IN
(SELECT id FROM tasks WHERE workspace_id = ?)`,
`DELETE FROM gitlab_task_mrs WHERE task_id IN
(SELECT id FROM tasks WHERE workspace_id = ?)`,
}
for _, query := range workspaceDeletes {
if _, err := tx.ExecContext(ctx, tx.Rebind(query), workspaceID); err != nil {
return E2EResetResult{}, err
}
}
reviewCount, err := reviewResult.RowsAffected()
if err != nil {
return E2EResetResult{}, err
}
issueCount, err := issueResult.RowsAffected()
if err != nil {
return E2EResetResult{}, err
}
if err := tx.Commit(); err != nil {
return E2EResetResult{}, err
}
return E2EResetResult{ReviewWatches: int(reviewCount), IssueWatches: int(issueCount)}, nil
}
// ResetWorkspaceE2E clears provider mock data, poller state, durable links,
// presets, and the workspace connection. It is called only by the backend's
// test-gated E2E reset route.
func (s *Service) ResetWorkspaceE2E(ctx context.Context, workspaceID string) (E2EResetResult, error) {
s.mu.RLock()
store := s.store
cachedClient := s.workspaceClients[workspaceID]
s.mu.RUnlock()
if store == nil {
return E2EResetResult{}, ErrNotConfigured
}
result, err := store.ResetWorkspaceE2E(ctx, workspaceID)
if err != nil {
return E2EResetResult{}, err
}
if mock, ok := cachedClient.(*MockClient); ok {
mock.Reset()
}
if err := s.DeleteConfigForWorkspace(ctx, workspaceID); err != nil {
return result, err
}
return result, nil
}