fix: stabilize inbox confirmation retries
This commit is contained in:
@@ -165,8 +165,8 @@ func (s *Service) Confirm(itemIdentity string, userID uint, selectedSuggestionId
|
||||
return err
|
||||
}
|
||||
if item.Status != "open" {
|
||||
result.CreatedCount, err = countConfirmedObjects(tx, item.ID, userID)
|
||||
return err
|
||||
result.CreatedCount = item.ConfirmedCount
|
||||
return nil
|
||||
}
|
||||
|
||||
selected, err := normalizeSelectedIdentities(selectedSuggestionIdentities)
|
||||
@@ -195,23 +195,14 @@ func (s *Service) Confirm(itemIdentity string, userID uint, selectedSuggestionId
|
||||
}
|
||||
result.CreatedCount++
|
||||
}
|
||||
return tx.Model(item).Update("status", "processed").Error
|
||||
return tx.Model(item).Updates(map[string]any{
|
||||
"status": "processed",
|
||||
"confirmed_count": result.CreatedCount,
|
||||
}).Error
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func countConfirmedObjects(tx *gorm.DB, inboxItemID, userID uint) (int, error) {
|
||||
total := int64(0)
|
||||
for _, model := range []any{&models.SenlinAgentTask{}, &models.SenlinAgentNote{}, &models.SenlinAgentSource{}} {
|
||||
var count int64
|
||||
if err := tx.Model(model).Where("source_inbox_item_id = ? AND created_by = ?", inboxItemID, userID).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += count
|
||||
}
|
||||
return int(total), nil
|
||||
}
|
||||
|
||||
func findOwnedInboxItem(tx *gorm.DB, identity string, userID uint, lock bool) (*models.SenlinAgentInboxItem, error) {
|
||||
var item models.SenlinAgentInboxItem
|
||||
query := tx.Model(&models.SenlinAgentInboxItem{}).
|
||||
|
||||
@@ -251,31 +251,42 @@ func TestConfirmIsTransactionalWhenASelectedWriteFails(t *testing.T) {
|
||||
require.Equal(t, "open", reloaded.Status)
|
||||
}
|
||||
|
||||
func TestRepeatedConfirmReturnsExistingResultWithoutDuplicateObjects(t *testing.T) {
|
||||
func TestRepeatedConfirmReturnsPersistedResultAfterRelatedObjectsChange(t *testing.T) {
|
||||
fixture := newInboxTestFixture(t)
|
||||
item := fixture.createInbox(t, fixture.owner.ID, fixture.project.ID)
|
||||
router := fixture.router(fixture.owner.ID, StaticAnalyzer{Suggestions: []Suggestion{
|
||||
{Kind: "task", Title: "只创建一次", Body: "重复确认不能复制"},
|
||||
{Kind: "note", Title: "不能追加创建", Body: "已确认后忽略不同建议"},
|
||||
{Kind: "note", Title: "确认结果", Body: "原始确认创建两个对象"},
|
||||
{Kind: "source", Title: "不能追加创建", Body: "已确认后忽略不同建议"},
|
||||
}})
|
||||
analysis := decodeInboxAnalysis(t, performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/analyze", nil))
|
||||
body := gin.H{"suggestionIds": []string{analysis.Suggestions[0].ID}}
|
||||
body := gin.H{"suggestionIds": []string{analysis.Suggestions[0].ID, analysis.Suggestions[1].ID}}
|
||||
|
||||
first := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", body)
|
||||
second := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", body)
|
||||
differentSelection := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", gin.H{
|
||||
"suggestionIds": []string{analysis.Suggestions[1].ID},
|
||||
})
|
||||
|
||||
require.Equal(t, http.StatusOK, first.Code)
|
||||
require.Equal(t, http.StatusOK, second.Code, second.Body.String())
|
||||
require.Equal(t, http.StatusOK, differentSelection.Code, differentSelection.Body.String())
|
||||
for _, response := range []*httptest.ResponseRecorder{first, second, differentSelection} {
|
||||
var result ConfirmResult
|
||||
require.NoError(t, json.Unmarshal(response.Body.Bytes(), &result))
|
||||
require.Equal(t, 1, result.CreatedCount)
|
||||
requireConfirmCreatedCount(t, first, 2)
|
||||
requireFormalObjectCounts(t, fixture.database, 1, 1, 0)
|
||||
|
||||
sourceInboxItemID := item.ID
|
||||
extraSource := models.SenlinAgentSource{
|
||||
ProjectID: item.ProjectID, CreatedBy: fixture.owner.ID, SourceInboxItemID: &sourceInboxItemID,
|
||||
Kind: "text", Title: "后续关联资料", ContentText: "不得改变历史确认结果",
|
||||
}
|
||||
requireFormalObjectCounts(t, fixture.database, 1, 0, 0)
|
||||
require.NoError(t, fixture.database.Create(&extraSource).Error)
|
||||
second := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", body)
|
||||
require.Equal(t, http.StatusOK, second.Code, second.Body.String())
|
||||
requireConfirmCreatedCount(t, second, 2)
|
||||
|
||||
var createdTask models.SenlinAgentTask
|
||||
require.NoError(t, fixture.database.Where("source_inbox_item_id = ?", item.ID).First(&createdTask).Error)
|
||||
require.NoError(t, fixture.database.Delete(&createdTask).Error)
|
||||
require.NoError(t, fixture.database.Delete(&extraSource).Error)
|
||||
differentSelection := performInboxJSON(t, router, http.MethodPost, "/api/v1/inbox/"+item.Identity+"/confirm", gin.H{
|
||||
"suggestionIds": []string{analysis.Suggestions[2].ID},
|
||||
})
|
||||
require.Equal(t, http.StatusOK, differentSelection.Code, differentSelection.Body.String())
|
||||
requireConfirmCreatedCount(t, differentSelection, 2)
|
||||
requireFormalObjectCounts(t, fixture.database, 0, 1, 0)
|
||||
}
|
||||
|
||||
func (fixture inboxTestFixture) router(userID uint, analyzer Analyzer) http.Handler {
|
||||
@@ -347,6 +358,13 @@ func requireFormalObjectCounts(t *testing.T, database *gorm.DB, tasks, notes, so
|
||||
}
|
||||
}
|
||||
|
||||
func requireConfirmCreatedCount(t *testing.T, response *httptest.ResponseRecorder, want int) {
|
||||
t.Helper()
|
||||
var result ConfirmResult
|
||||
require.NoError(t, json.Unmarshal(response.Body.Bytes(), &result))
|
||||
require.Equal(t, want, result.CreatedCount)
|
||||
}
|
||||
|
||||
func requireSourceInboxIdentity(t *testing.T, item models.SenlinAgentInboxItem, sourceID *uint, sourceIdentity *string) {
|
||||
t.Helper()
|
||||
require.NotNil(t, sourceID)
|
||||
|
||||
@@ -13,6 +13,7 @@ type SenlinAgentInboxItem struct {
|
||||
Title string
|
||||
Body string
|
||||
Status string `gorm:"not null;default:open"`
|
||||
ConfirmedCount int `gorm:"not null;default:0"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user