feat: sync message translation support
This commit is contained in:
parent
cbccd6a8d9
commit
ea6cc72886
26 changed files with 1189 additions and 0 deletions
108
internal/app/translation/service_test.go
Normal file
108
internal/app/translation/service_test.go
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package translation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store/memory"
|
||||
)
|
||||
|
||||
type testLimiter struct{ cost int }
|
||||
|
||||
func (l *testLimiter) AllowN(_ context.Context, _ string, cost, _ int, _ time.Duration) (bool, int, error) {
|
||||
l.cost = cost
|
||||
return true, 0, nil
|
||||
}
|
||||
|
||||
type testProvider struct {
|
||||
translate func([]domain.TranslationText) ([]domain.TranslationText, error)
|
||||
}
|
||||
|
||||
func (testProvider) Name() string { return "test" }
|
||||
func (p testProvider) Translate(_ context.Context, texts []domain.TranslationText, _, _ string) ([]domain.TranslationText, error) {
|
||||
return p.translate(texts)
|
||||
}
|
||||
|
||||
type testPrivateMessages struct{ messages []domain.Message }
|
||||
|
||||
func (s testPrivateMessages) GetMessages(_ context.Context, _ int64, _ []int) (domain.MessageList, error) {
|
||||
return domain.MessageList{Messages: append([]domain.Message(nil), s.messages...)}, nil
|
||||
}
|
||||
|
||||
func TestTranslateDirectTextPreservesBatchOrder(t *testing.T) {
|
||||
limiter := &testLimiter{}
|
||||
svc := NewService(nil, nil, memory.NewDialogStore(), WithRateLimiter(limiter, 60, time.Minute), WithProviders(testProvider{translate: func(in []domain.TranslationText) ([]domain.TranslationText, error) {
|
||||
out := make([]domain.TranslationText, len(in))
|
||||
for i := range in {
|
||||
out[i].Text = "zh:" + in[i].Text
|
||||
}
|
||||
return out, nil
|
||||
}}))
|
||||
got, err := svc.Translate(context.Background(), domain.TranslationRequest{
|
||||
UserID: 1,
|
||||
Texts: []domain.TranslationText{{Text: "one"}, {Text: "two"}},
|
||||
ToLang: "zh",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Translate: %v", err)
|
||||
}
|
||||
if len(got.Texts) != 2 || got.Texts[0].Text != "zh:one" || got.Texts[1].Text != "zh:two" {
|
||||
t.Fatalf("Translate result = %#v", got.Texts)
|
||||
}
|
||||
if limiter.cost != 2 {
|
||||
t.Fatalf("rate limit cost = %d, want 2 text items", limiter.cost)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateMessageIDsRejectsWrongPeerAndMissingID(t *testing.T) {
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 2}
|
||||
svc := NewService(testPrivateMessages{messages: []domain.Message{
|
||||
{ID: 10, Peer: peer, Body: "visible"},
|
||||
{ID: 11, Peer: domain.Peer{Type: domain.PeerTypeUser, ID: 3}, Body: "wrong peer"},
|
||||
}}, nil, memory.NewDialogStore(), WithProviders(testProvider{translate: func(in []domain.TranslationText) ([]domain.TranslationText, error) {
|
||||
return in, nil
|
||||
}}))
|
||||
for _, ids := range [][]int{{10, 11}, {10, 12}} {
|
||||
_, err := svc.Translate(context.Background(), domain.TranslationRequest{UserID: 1, Peer: peer, IDs: ids, ToLang: "en"})
|
||||
if !errors.Is(err, domain.ErrTranslationMessageInvalid) {
|
||||
t.Fatalf("Translate ids %v err = %v, want message invalid", ids, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTranslateRejectsOversizeAndProviderShapeMismatch(t *testing.T) {
|
||||
svc := NewService(nil, nil, memory.NewDialogStore(), WithProviders(testProvider{translate: func(in []domain.TranslationText) ([]domain.TranslationText, error) {
|
||||
return in[:len(in)-1], nil
|
||||
}}))
|
||||
texts := make([]domain.TranslationText, domain.MaxTranslationTexts+1)
|
||||
for i := range texts {
|
||||
texts[i].Text = "x"
|
||||
}
|
||||
if _, err := svc.Translate(context.Background(), domain.TranslationRequest{UserID: 1, Texts: texts, ToLang: "en"}); !errors.Is(err, domain.ErrTranslationInputTooLong) {
|
||||
t.Fatalf("oversize err = %v", err)
|
||||
}
|
||||
if _, err := svc.Translate(context.Background(), domain.TranslationRequest{UserID: 1, Texts: texts[:2], ToLang: "en"}); !errors.Is(err, domain.ErrTranslationProviderUnavailable) {
|
||||
t.Fatalf("shape mismatch err = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeerDisabledIsAccountAndPeerScoped(t *testing.T) {
|
||||
settings := memory.NewDialogStore()
|
||||
svc := NewService(nil, nil, settings)
|
||||
peer := domain.Peer{Type: domain.PeerTypeUser, ID: 2}
|
||||
if changed, err := svc.SetPeerDisabled(context.Background(), 1, peer, true); err != nil || !changed {
|
||||
t.Fatalf("disable = %v/%v", changed, err)
|
||||
}
|
||||
if disabled, _ := svc.PeerDisabled(context.Background(), 1, peer); !disabled {
|
||||
t.Fatal("owner preference not persisted")
|
||||
}
|
||||
if disabled, _ := svc.PeerDisabled(context.Background(), 2, peer); disabled {
|
||||
t.Fatal("preference leaked to another account")
|
||||
}
|
||||
if changed, err := svc.SetPeerDisabled(context.Background(), 1, peer, false); err != nil || !changed {
|
||||
t.Fatalf("enable = %v/%v", changed, err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue