From a762430bca43fbd56de55702e97d41ba451e3543 Mon Sep 17 00:00:00 2001 From: Mazyar Date: Sun, 5 Jul 2026 21:01:42 +0330 Subject: [PATCH] Add unit tests for tender recommendation translation handling - 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. --- .../tender/recommendation_translation_test.go | 134 ++++++++++++++++++ internal/tender/service.go | 35 ++++- 2 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 internal/tender/recommendation_translation_test.go diff --git a/internal/tender/recommendation_translation_test.go b/internal/tender/recommendation_translation_test.go new file mode 100644 index 0000000..00458dc --- /dev/null +++ b/internal/tender/recommendation_translation_test.go @@ -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) + } +} diff --git a/internal/tender/service.go b/internal/tender/service.go index d499dca..ecf874b 100644 --- a/internal/tender/service.go +++ b/internal/tender/service.go @@ -995,7 +995,13 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat lang := s.pickResponseLanguage(form.Language) 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 { tenderRef := strings.TrimSpace(rec.TenderID) tender, ok := tenderByRef[tenderRef] @@ -1016,11 +1022,12 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat continue } - resp := tender.ToResponseWithLanguage(lang) - resp.Rank = rec.Rank - resp.Analysis = rec.Analysis - resp.ProcedureRef = tenderRef - ordered = append(ordered, *resp) + ordered = append(ordered, rankedRecommendedTender{ + tender: tender, + rank: rec.Rank, + analysis: rec.Analysis, + tenderRef: tenderRef, + }) } total := int64(len(ordered)) @@ -1033,12 +1040,26 @@ func (s *tenderService) Recommend(ctx context.Context, form *SearchForm, paginat 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{ - Tenders: ordered[start:end], + Tenders: paged, Metadata: pagination.ListMeta(total, "", end < len(ordered), start), }, 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 { for _, s := range allowed { if string(status) == strings.TrimSpace(s) {