package service import ( "context" "io" "net/http" "strings" "testing" "bugulma/backend/internal/domain" ) // minimal mocks type mockLocRepo struct { fieldValues map[string]string // key: entityID|field -> value idsByField map[string][]string } func (m *mockLocRepo) GetEntitiesNeedingTranslation(ctx context.Context, entityType, field, targetLocale string, limit int) ([]string, error) { return m.idsByField[field], nil } func (m *mockLocRepo) GetEntityFieldValue(ctx context.Context, entityType, entityID, field string) (string, error) { k := entityID + "|" + field return m.fieldValues[k], nil } func (m *mockLocRepo) FindExistingTranslationByRussianText(ctx context.Context, entityType, field, targetLocale, russianText string) (string, error) { return "", nil } // Unused methods to satisfy interface - implement as noops func (m *mockLocRepo) Create(ctx context.Context, loc *domain.Localization) error { return nil } func (m *mockLocRepo) GetByEntityAndField(ctx context.Context, entityType, entityID, field, locale string) (*domain.Localization, error) { return nil, nil } func (m *mockLocRepo) GetAllByEntity(ctx context.Context, entityType, entityID string) ([]*domain.Localization, error) { return nil, nil } func (m *mockLocRepo) Update(ctx context.Context, loc *domain.Localization) error { return nil } func (m *mockLocRepo) Delete(ctx context.Context, id string) error { return nil } func (m *mockLocRepo) GetByEntityTypeAndLocale(ctx context.Context, entityType, locale string) ([]*domain.Localization, error) { return nil, nil } func (m *mockLocRepo) GetAllLocales(ctx context.Context) ([]string, error) { return nil, nil } func (m *mockLocRepo) GetSupportedLocalesForEntity(ctx context.Context, entityType, entityID string) ([]string, error) { return nil, nil } func (m *mockLocRepo) BulkCreate(ctx context.Context, localizations []*domain.Localization) error { return nil } func (m *mockLocRepo) BulkDelete(ctx context.Context, ids []string) error { return nil } func (m *mockLocRepo) SearchLocalizations(ctx context.Context, query string, locale string, limit int) ([]*domain.Localization, error) { return nil, nil } func (m *mockLocRepo) GetTranslationReuseCandidates(ctx context.Context, entityType, field, locale string) ([]domain.ReuseCandidate, error) { return nil, nil } func (m *mockLocRepo) GetTranslationCountsByEntity(ctx context.Context) (map[string]map[string]int, error) { return nil, nil } // fake roundtripper to mock HTTP responses from the translation service type fakeRoundTripper struct { responseBody string } func (f *fakeRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { body := io.NopCloser(strings.NewReader(f.responseBody)) return &http.Response{StatusCode: 200, Body: body, Header: http.Header{"Content-Type": {"application/json"}}}, nil } type mockLocService struct { saved map[string]string // key: entityID|field|locale -> value } func (m *mockLocService) GetLocalizedValue(entityType, entityID, field, locale string) (string, error) { return "", nil } func (m *mockLocService) SetLocalizedValue(entityType, entityID, field, locale, value string) error { k := entityID + "|" + field + "|" + locale m.saved[k] = value return nil } func (m *mockLocService) GetAllLocalizedValues(entityType, entityID string) (map[string]map[string]string, error) { return nil, nil } func (m *mockLocService) GetLocalizedEntity(entityType, entityID, locale string) (map[string]string, error) { return nil, nil } func (m *mockLocService) GetSupportedLocalesForEntity(entityType, entityID string) ([]string, error) { return nil, nil } func (m *mockLocService) DeleteLocalizedValue(entityType, entityID, field, locale string) error { return nil } func (m *mockLocService) BulkSetLocalizedValues(entityType, entityID string, values map[string]map[string]string) error { return nil } func (m *mockLocService) GetAllLocales() ([]string, error) { return nil, nil } func (m *mockLocService) SearchLocalizations(query, locale string, limit int) ([]*domain.Localization, error) { return nil, nil } func (m *mockLocService) ApplyLocalizationToEntity(entity domain.Localizable, locale string) error { return nil } func TestBulkTranslateData_Simple(t *testing.T) { locRepo := &mockLocRepo{ fieldValues: map[string]string{"org-1|name": "Пример"}, idsByField: map[string][]string{"name": {"org-1"}}, } locSvc := &mockLocService{saved: make(map[string]string)} // create a TranslationService with a fake client that returns canned response ts := NewTranslationService("http://fake-ollama", "test-model") ts.client = &http.Client{Transport: &fakeRoundTripper{responseBody: `{"model":"test","response":"translated:Пример:en","done":true}`}} cache := NewTranslationCacheService(locRepo, locSvc) svc := &I18nService{ locService: locSvc, locRepo: locRepo, translationSvc: ts, cacheService: cache, } results, err := svc.BulkTranslateData(context.Background(), "organization", nil, "en", []string{"name"}) if err != nil { t.Fatalf("expected no error, got %v", err) } if len(results) != 1 { t.Fatalf("expected results for 1 entity, got %d", len(results)) } if results["org-1"]["name"] != "translated:Пример:en" { t.Fatalf("unexpected translation: %v", results["org-1"]["name"]) } // Check that translation was saved via locService saved := locSvc.saved["org-1|name|en"] if saved != "translated:Пример:en" { t.Fatalf("expected saved translation, got %v", saved) } }