Files

133 lines
5.1 KiB
Go
Raw Permalink Normal View History

package service
import (
"context"
"testing"
"github.com/Tencent/WeKnora/internal/types"
"github.com/Tencent/WeKnora/internal/types/interfaces"
"github.com/stretchr/testify/require"
)
// fakeWikiPages serves the pages a knowledge base already owns, so identity
// resolution can be exercised without a database.
type fakeWikiPages struct {
interfaces.WikiPageService
bySlug map[string]*types.WikiPageLite
}
func (f fakeWikiPages) ListBySlugs(ctx context.Context, kbID string, slugs []string) (map[string]*types.WikiPageLite, error) {
out := make(map[string]*types.WikiPageLite, len(slugs))
for _, slug := range slugs {
if page := f.bySlug[slug]; page != nil {
out[slug] = page
}
}
return out, nil
}
func (f fakeWikiPages) FindPagesByNormalizedTitles(ctx context.Context, kbID, pageType string, identities []string) ([]*types.WikiPageLite, error) {
wanted := make(map[string]bool, len(identities))
for _, identity := range identities {
wanted[identity] = true
}
var out []*types.WikiPageLite
for _, page := range f.bySlug {
if page.PageType == pageType && wanted[normalizeWikiIdentityTitle(page.Title)] {
out = append(out, page)
}
}
return out, nil
}
func pageTitle(entityType, slug, title string) *types.WikiPageLite {
return &types.WikiPageLite{Slug: slug, Title: title, PageType: entityType}
}
// A model-written spelling variant must resolve onto the page that already owns the
// slug instead of ending the document: window title "G 点" against page "G点".
func TestWikiIdentityTargetsAdoptTheOwningPagesSpelling(t *testing.T) {
service := &wikiIngestService{wikiService: fakeWikiPages{bySlug: map[string]*types.WikiPageLite{
"entity/g-dian": pageTitle(types.WikiPageTypeEntity, "entity/g-dian", "G点"),
}}}
result := combinedExtraction{Entities: []extractedItem{{
Name: "G 点", Slug: "entity/g-dian", Description: "d", Details: "x",
}}}
out, dropped, err := service.resolveWikiIdentityTargets(context.Background(), "kb", result)
require.NoError(t, err)
require.Equal(t, 0, dropped, "a spelling-only difference is not a drop")
require.Len(t, out.Entities, 1)
require.Equal(t, "G点", out.Entities[0].Name, "the page keeps owning its canonical title")
require.Equal(t, "entity/g-dian", out.Entities[0].Slug)
}
// A different identity claiming an owned slug is dropped and counted, and the rest
// of the document continues instead of failing.
func TestWikiIdentityTargetsDropTheIntruderAndKeepTheRest(t *testing.T) {
service := &wikiIngestService{wikiService: fakeWikiPages{bySlug: map[string]*types.WikiPageLite{
"entity/du-lei-si": pageTitle(types.WikiPageTypeEntity, "entity/du-lei-si", "杜蕾斯"),
}}}
result := combinedExtraction{
Entities: []extractedItem{
{Name: "Durex", Slug: "entity/du-lei-si", Description: "d", Details: "x"},
{Name: "跳蛋", Slug: "entity/tiao-dan", Description: "d", Details: "x"},
},
}
out, dropped, err := service.resolveWikiIdentityTargets(context.Background(), "kb", result)
require.NoError(t, err)
require.Equal(t, 1, dropped)
require.Len(t, out.Entities, 1)
require.Equal(t, "跳蛋", out.Entities[0].Name)
require.Equal(t, 1, out.CandidatePruning.SlugConflicts, "the drop is counted for audit")
}
// A slug owned by another page type cannot be taken over silently.
func TestWikiIdentityTargetsRejectACrossTypeSlug(t *testing.T) {
service := &wikiIngestService{wikiService: fakeWikiPages{bySlug: map[string]*types.WikiPageLite{
"entity/sm": pageTitle(types.WikiPageTypeConcept, "entity/sm", "SM"),
}}}
result := combinedExtraction{
Entities: []extractedItem{
{Name: "SM", Slug: "entity/sm", Description: "d", Details: "x"},
{Name: "跳蛋", Slug: "entity/tiao-dan", Description: "d", Details: "x"},
},
}
out, dropped, err := service.resolveWikiIdentityTargets(context.Background(), "kb", result)
require.NoError(t, err)
require.Equal(t, 1, dropped)
require.Len(t, out.Entities, 1)
require.Equal(t, "跳蛋", out.Entities[0].Name)
}
// A set that loses every candidate still fails closed rather than publishing nothing
// under the pretence of a successful document.
func TestWikiIdentityTargetsFailClosedWhenEverythingCollides(t *testing.T) {
service := &wikiIngestService{wikiService: fakeWikiPages{bySlug: map[string]*types.WikiPageLite{
"entity/sm": pageTitle(types.WikiPageTypeConcept, "entity/sm", "SM"),
}}}
result := combinedExtraction{Entities: []extractedItem{
{Name: "SM", Slug: "entity/sm", Description: "d", Details: "x"},
}}
_, _, err := service.resolveWikiIdentityTargets(context.Background(), "kb", result)
require.Error(t, err)
require.Contains(t, err.Error(), "wiki_identity_slug_conflict")
}
// Without a wiki repository the resolution is a pass-through, so Lite mode keeps
// working and reports no drops.
func TestWikiIdentityTargetsPassThroughWithoutRepository(t *testing.T) {
service := &wikiIngestService{}
result := combinedExtraction{Entities: []extractedItem{
{Name: "跳蛋", Slug: "entity/tiao-dan", Description: "d", Details: "x"},
}}
out, dropped, err := service.resolveWikiIdentityTargets(context.Background(), "kb", result)
require.NoError(t, err)
require.Equal(t, 0, dropped)
require.Len(t, out.Entities, 1)
}