WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type wikiEnqueueFailureKnowledgeRepo struct {
|
||||
interfaces.KnowledgeRepository
|
||||
knowledge *types.Knowledge
|
||||
expectedSubtasks int
|
||||
}
|
||||
|
||||
func (r *wikiEnqueueFailureKnowledgeRepo) GetKnowledgeByIDOnly(
|
||||
context.Context,
|
||||
string,
|
||||
) (*types.Knowledge, error) {
|
||||
return r.knowledge, nil
|
||||
}
|
||||
|
||||
func (r *wikiEnqueueFailureKnowledgeRepo) SetFinalizing(
|
||||
_ context.Context,
|
||||
_ string,
|
||||
expectedSubtasks int,
|
||||
) (bool, error) {
|
||||
r.expectedSubtasks = expectedSubtasks
|
||||
r.knowledge.ParseStatus = types.ParseStatusFinalizing
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *wikiEnqueueFailureKnowledgeRepo) UpdateKnowledgeColumn(
|
||||
context.Context,
|
||||
string,
|
||||
string,
|
||||
interface{},
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type wikiEnqueueFailureKBService struct {
|
||||
interfaces.KnowledgeBaseService
|
||||
kb *types.KnowledgeBase
|
||||
}
|
||||
|
||||
func (s *wikiEnqueueFailureKBService) GetKnowledgeBaseByIDOnly(
|
||||
context.Context,
|
||||
string,
|
||||
) (*types.KnowledgeBase, error) {
|
||||
return s.kb, nil
|
||||
}
|
||||
|
||||
type wikiEnqueueFailureChunkService struct {
|
||||
interfaces.ChunkService
|
||||
chunks []*types.Chunk
|
||||
}
|
||||
|
||||
func (s *wikiEnqueueFailureChunkService) ListChunksByKnowledgeID(
|
||||
context.Context,
|
||||
string,
|
||||
) ([]*types.Chunk, error) {
|
||||
return s.chunks, nil
|
||||
}
|
||||
|
||||
type wikiEnqueueFailureTaskQueue struct {
|
||||
interfaces.TaskEnqueuer
|
||||
taskTypes []string
|
||||
wikiErr error
|
||||
}
|
||||
|
||||
func (q *wikiEnqueueFailureTaskQueue) Enqueue(
|
||||
task *asynq.Task,
|
||||
_ ...asynq.Option,
|
||||
) (*asynq.TaskInfo, error) {
|
||||
q.taskTypes = append(q.taskTypes, task.Type())
|
||||
if task.Type() == types.TypeWikiIngest {
|
||||
return nil, q.wikiErr
|
||||
}
|
||||
return &asynq.TaskInfo{ID: "queued", Type: task.Type()}, nil
|
||||
}
|
||||
|
||||
type wikiEnqueueFailurePendingRepo struct {
|
||||
interfaces.TaskPendingOpsRepository
|
||||
seedErr error
|
||||
seedCalls int
|
||||
seededOp *types.TaskPendingOp
|
||||
knowledgeRepo *wikiEnqueueFailureKnowledgeRepo
|
||||
}
|
||||
|
||||
func (r *wikiEnqueueFailurePendingRepo) SeedKnowledgeFinalizingWithPendingOp(
|
||||
_ context.Context,
|
||||
_ string,
|
||||
expectedSubtasks int,
|
||||
op *types.TaskPendingOp,
|
||||
) (bool, error) {
|
||||
r.seedCalls++
|
||||
if r.seedErr != nil {
|
||||
return false, r.seedErr
|
||||
}
|
||||
r.seededOp = op
|
||||
r.knowledgeRepo.expectedSubtasks = expectedSubtasks
|
||||
r.knowledgeRepo.knowledge.ParseStatus = types.ParseStatusFinalizing
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func newWikiEnqueueTestService(
|
||||
knowledgeID string,
|
||||
pendingRepo *wikiEnqueueFailurePendingRepo,
|
||||
queue *wikiEnqueueFailureTaskQueue,
|
||||
) (*KnowledgePostProcessService, *wikiEnqueueFailureKnowledgeRepo) {
|
||||
repo := &wikiEnqueueFailureKnowledgeRepo{
|
||||
knowledge: &types.Knowledge{
|
||||
ID: knowledgeID,
|
||||
ParseStatus: types.ParseStatusProcessing,
|
||||
},
|
||||
}
|
||||
pendingRepo.knowledgeRepo = repo
|
||||
return &KnowledgePostProcessService{
|
||||
knowledgeRepo: repo,
|
||||
kbService: &wikiEnqueueFailureKBService{kb: &types.KnowledgeBase{
|
||||
ID: "kb-wiki",
|
||||
IndexingStrategy: types.IndexingStrategy{
|
||||
WikiEnabled: true,
|
||||
},
|
||||
}},
|
||||
chunkService: &wikiEnqueueFailureChunkService{chunks: []*types.Chunk{
|
||||
{ID: "chunk-1", ChunkType: types.ChunkTypeText},
|
||||
}},
|
||||
taskEnqueuer: queue,
|
||||
pendingRepo: pendingRepo,
|
||||
}, repo
|
||||
}
|
||||
|
||||
func newWikiEnqueuePostProcessTask(t *testing.T, knowledgeID string) *asynq.Task {
|
||||
t.Helper()
|
||||
payload, err := json.Marshal(types.KnowledgePostProcessPayload{
|
||||
TenantID: 7,
|
||||
KnowledgeID: knowledgeID,
|
||||
KnowledgeBaseID: "kb-wiki",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return asynq.NewTask(types.TypeKnowledgePostProcess, payload)
|
||||
}
|
||||
|
||||
func TestKnowledgePostProcessAtomicallySeedsWikiSlot(t *testing.T) {
|
||||
const knowledgeID = "knowledge-wiki-enqueue-failure"
|
||||
tests := []struct {
|
||||
name string
|
||||
pendingErr error
|
||||
wantTasks []string
|
||||
wantStatus string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "pending op persistence fails",
|
||||
pendingErr: errors.New("postgres unavailable"),
|
||||
wantStatus: types.ParseStatusProcessing,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "pending op and trigger succeed",
|
||||
wantTasks: []string{types.TypeSummaryGeneration, types.TypeWikiIngest},
|
||||
wantStatus: types.ParseStatusFinalizing,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
pendingRepo := &wikiEnqueueFailurePendingRepo{seedErr: test.pendingErr}
|
||||
queue := &wikiEnqueueFailureTaskQueue{}
|
||||
service, repo := newWikiEnqueueTestService(knowledgeID, pendingRepo, queue)
|
||||
|
||||
err := service.Handle(context.Background(), newWikiEnqueuePostProcessTask(t, knowledgeID))
|
||||
|
||||
if test.wantErr {
|
||||
require.ErrorIs(t, err, test.pendingErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, repo.expectedSubtasks, "summary and wiki each seed one slot")
|
||||
require.NotNil(t, pendingRepo.seededOp)
|
||||
assert.Equal(t, knowledgeID, pendingRepo.seededOp.DedupKey)
|
||||
}
|
||||
assert.Equal(t, test.wantStatus, repo.knowledge.ParseStatus)
|
||||
assert.Equal(t, test.wantTasks, queue.taskTypes)
|
||||
assert.Equal(t, 1, pendingRepo.seedCalls)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKnowledgePostProcessRetriesWikiTriggerWithoutDoubleAccounting(t *testing.T) {
|
||||
const knowledgeID = "knowledge-wiki-trigger-retry"
|
||||
wikiErr := errors.New("redis unavailable")
|
||||
pendingRepo := &wikiEnqueueFailurePendingRepo{}
|
||||
queue := &wikiEnqueueFailureTaskQueue{wikiErr: wikiErr}
|
||||
service, repo := newWikiEnqueueTestService(knowledgeID, pendingRepo, queue)
|
||||
task := newWikiEnqueuePostProcessTask(t, knowledgeID)
|
||||
|
||||
err := service.Handle(context.Background(), task)
|
||||
|
||||
require.ErrorIs(t, err, wikiErr)
|
||||
assert.Equal(t, 2, repo.expectedSubtasks, "summary and wiki each seed one slot")
|
||||
assert.Equal(t, 1, pendingRepo.seedCalls)
|
||||
assert.Equal(t, []string{types.TypeSummaryGeneration, types.TypeWikiIngest}, queue.taskTypes)
|
||||
|
||||
queue.wikiErr = nil
|
||||
err = service.Handle(context.Background(), task)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, pendingRepo.seedCalls, "retry must not append another pending op")
|
||||
assert.Equal(t,
|
||||
[]string{types.TypeSummaryGeneration, types.TypeWikiIngest, types.TypeWikiIngest},
|
||||
queue.taskTypes,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user