Add unit tests for tender recommendation translation handling
continuous-integration/drone/push Build is passing

- Introduced a new test file `recommendation_translation_test.go` to validate the behavior of the tender recommendation service regarding translation handling.
- Implemented tests to ensure that the service correctly applies stored translations and retains original text when no translation is available.
- Enhanced the `Recommend` method in the tender service to utilize a structured approach for managing recommended tenders, improving clarity and maintainability.

This update improves the testing coverage for translation handling in tender recommendations, ensuring accurate responses based on available translations and original content.
This commit is contained in:
Mazyar
2026-07-05 21:01:42 +03:30
parent 3221a31ac8
commit a762430bca
2 changed files with 162 additions and 7 deletions
@@ -0,0 +1,134 @@
package tender
import (
"context"
"io"
"testing"
"tm/pkg/ai_summarizer"
"tm/pkg/logger"
)
type noopTenderTestLogger struct{}
func (noopTenderTestLogger) Debug(string, map[string]interface{}) {}
func (noopTenderTestLogger) Info(string, map[string]interface{}) {}
func (noopTenderTestLogger) Warn(string, map[string]interface{}) {}
func (noopTenderTestLogger) Error(string, map[string]interface{}) {}
func (noopTenderTestLogger) Fatal(string, map[string]interface{}) {}
func (l noopTenderTestLogger) WithFields(map[string]interface{}) logger.Logger {
return l
}
func (noopTenderTestLogger) Sync() error { return nil }
type stubRecommendationTranslationStorage struct {
translations map[string]ai_summarizer.StoredTranslation
}
func (s stubRecommendationTranslationStorage) GetSummaryFromStorage(context.Context, string, string) (string, error) {
return "", ai_summarizer.ErrSummaryNotReady
}
func (s stubRecommendationTranslationStorage) GetDocumentSummariesFromStorage(context.Context, string, string) ([]ai_summarizer.DocumentSummary, error) {
return nil, nil
}
func (s stubRecommendationTranslationStorage) GetTranslationFromStorage(_ context.Context, contractFolderID, noticePublicationID, language string) (ai_summarizer.StoredTranslation, error) {
key := contractFolderID + "|" + noticePublicationID + "|" + language
if translation, ok := s.translations[key]; ok {
return translation, nil
}
return ai_summarizer.StoredTranslation{}, ai_summarizer.ErrTranslationNotReady
}
func (s stubRecommendationTranslationStorage) ListDocuments(context.Context, string, string) ([]ai_summarizer.StoredDocument, error) {
return nil, nil
}
func (s stubRecommendationTranslationStorage) DownloadDocument(context.Context, string) (io.ReadCloser, error) {
return nil, ai_summarizer.ErrObjectNotFound
}
func TestBuildRecommendedTenderResponseAppliesStoredTranslation(t *testing.T) {
service := &tenderService{
aiSummarizerStorage: stubRecommendationTranslationStorage{
translations: map[string]ai_summarizer.StoredTranslation{
"folder-1|00123456-2026|fr": {
Title: "Titre traduit",
Description: "Description traduite",
},
},
},
defaultLanguage: "en",
logger: noopTenderTestLogger{},
}
tender := Tender{
ContractFolderID: "folder-1",
NoticePublicationID: "00123456-2026",
Title: "Original title",
Description: "Original description",
}
resp := service.buildRecommendedTenderResponse(
context.Background(),
tender,
3,
"Strong fit",
"PROC_folder-1/00123456-2026",
"fr",
)
if resp.Title != "Titre traduit" {
t.Fatalf("expected translated title, got %q", resp.Title)
}
if resp.Description != "Description traduite" {
t.Fatalf("expected translated description, got %q", resp.Description)
}
if resp.Language != "fr" {
t.Fatalf("expected response language fr, got %q", resp.Language)
}
if resp.Rank != 3 {
t.Fatalf("expected rank 3, got %d", resp.Rank)
}
if resp.Analysis != "Strong fit" {
t.Fatalf("expected analysis to be preserved, got %q", resp.Analysis)
}
if resp.ProcedureRef != "PROC_folder-1/00123456-2026" {
t.Fatalf("expected procedure ref to be preserved, got %q", resp.ProcedureRef)
}
}
func TestBuildRecommendedTenderResponseKeepsOriginalTextWithoutStoredTranslation(t *testing.T) {
service := &tenderService{
aiSummarizerStorage: stubRecommendationTranslationStorage{},
defaultLanguage: "en",
logger: noopTenderTestLogger{},
}
tender := Tender{
ContractFolderID: "folder-1",
NoticePublicationID: "00123456-2026",
Title: "Original title",
Description: "Original description",
}
resp := service.buildRecommendedTenderResponse(
context.Background(),
tender,
1,
"Fallback",
"PROC_folder-1/00123456-2026",
"fr",
)
if resp.Title != "Original title" {
t.Fatalf("expected original title, got %q", resp.Title)
}
if resp.Description != "Original description" {
t.Fatalf("expected original description, got %q", resp.Description)
}
if resp.Language != "" {
t.Fatalf("expected empty language when no translation exists, got %q", resp.Language)
}
}
+28 -7
View File
@@ -995,7 +995,13 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat
lang := s.pickResponseLanguage(form.Language) lang := s.pickResponseLanguage(form.Language)
now := time.Now().Unix() now := time.Now().Unix()
ordered := make([]TenderResponse, 0, len(recommendations)) type rankedRecommendedTender struct {
tender Tender
rank int
analysis string
tenderRef string
}
ordered := make([]rankedRecommendedTender, 0, len(recommendations))
for _, rec := range recommendations { for _, rec := range recommendations {
tenderRef := strings.TrimSpace(rec.TenderID) tenderRef := strings.TrimSpace(rec.TenderID)
tender, ok := tenderByRef[tenderRef] tender, ok := tenderByRef[tenderRef]
@@ -1016,11 +1022,12 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat
continue continue
} }
resp := tender.ToResponseWithLanguage(lang) ordered = append(ordered, rankedRecommendedTender{
resp.Rank = rec.Rank tender: tender,
resp.Analysis = rec.Analysis rank: rec.Rank,
resp.ProcedureRef = tenderRef analysis: rec.Analysis,
ordered = append(ordered, *resp) tenderRef: tenderRef,
})
} }
total := int64(len(ordered)) total := int64(len(ordered))
@@ -1033,12 +1040,26 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat
end = len(ordered) end = len(ordered)
} }
paged := make([]TenderResponse, 0, end-start)
for _, item := range ordered[start:end] {
paged = append(paged, s.buildRecommendedTenderResponse(ctx, item.tender, item.rank, item.analysis, item.tenderRef, lang))
}
return &SearchResponse{ return &SearchResponse{
Tenders: ordered[start:end], Tenders: paged,
Metadata: pagination.ListMeta(total, "", end < len(ordered), start), Metadata: pagination.ListMeta(total, "", end < len(ordered), start),
}, nil }, nil
} }
func (s *tenderService) buildRecommendedTenderResponse(ctx context.Context, tender Tender, rank int, analysis, tenderRef, language string) TenderResponse {
resp := tender.ToResponseWithLanguage(language)
s.enrichWithTranslation(ctx, &tender, resp, language)
resp.Rank = rank
resp.Analysis = analysis
resp.ProcedureRef = tenderRef
return *resp
}
func statusAllowed(status TenderStatus, allowed []string) bool { func statusAllowed(status TenderStatus, allowed []string) bool {
for _, s := range allowed { for _, s := range allowed {
if string(status) == strings.TrimSpace(s) { if string(status) == strings.TrimSpace(s) {