mirror of
https://github.com/SamyRai/tercul-backend.git
synced 2025-12-27 05:11:34 +00:00
147 lines
5.5 KiB
Go
147 lines
5.5 KiB
Go
package linguistics
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Mocks for provider interfaces
|
|
|
|
type mockLangDetector struct{ lang string; err error }
|
|
func (m mockLangDetector) DetectLanguage(text string) (string, error) { return m.lang, m.err }
|
|
|
|
type mockSentimentProvider struct{ score float64; err error }
|
|
func (m mockSentimentProvider) Score(text string, language string) (float64, error) { return m.score, m.err }
|
|
|
|
type mockKeywordProvider struct{ kws []Keyword; err error }
|
|
func (m mockKeywordProvider) Extract(text string, language string) ([]Keyword, error) { return m.kws, m.err }
|
|
|
|
func TestAnalyzeText_Empty(t *testing.T) {
|
|
a := NewBasicTextAnalyzer()
|
|
res, err := a.AnalyzeText(context.Background(), "", "")
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, res)
|
|
assert.Equal(t, 0, res.WordCount)
|
|
assert.Equal(t, 0, res.SentenceCount)
|
|
assert.Equal(t, 0.0, res.Sentiment)
|
|
assert.Len(t, res.Keywords, 0)
|
|
}
|
|
|
|
func TestAnalyzeText_ProvidersAndLangDetection(t *testing.T) {
|
|
// Arrange
|
|
a := NewBasicTextAnalyzer().
|
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
|
WithSentimentProvider(mockSentimentProvider{score: 0.75}).
|
|
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "golang", Relevance: 0.42}}})
|
|
|
|
text := "Go is great. Go makes concurrency easier."
|
|
|
|
// Act
|
|
res, err := a.AnalyzeText(context.Background(), text, "")
|
|
|
|
// Assert
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
assert.InDelta(t, 0.75, res.Sentiment, 1e-9)
|
|
require.Len(t, res.Keywords, 1)
|
|
assert.Equal(t, "golang", res.Keywords[0].Text)
|
|
assert.InDelta(t, 0.42, res.Keywords[0].Relevance, 1e-9)
|
|
// Basic stats make sense
|
|
assert.Greater(t, res.WordCount, 0)
|
|
assert.Greater(t, res.SentenceCount, 0)
|
|
// Readability is clamped to [0,100]
|
|
assert.GreaterOrEqual(t, res.ReadabilityScore, 0.0)
|
|
assert.LessOrEqual(t, res.ReadabilityScore, 100.0)
|
|
assert.Equal(t, "Simplified Flesch-Kincaid", res.ReadabilityMethod)
|
|
}
|
|
|
|
func TestAnalyzeText_FallbackOnProviderError(t *testing.T) {
|
|
// Arrange providers that fail so analyzer uses internal fallbacks
|
|
a := NewBasicTextAnalyzer().
|
|
WithSentimentProvider(mockSentimentProvider{err: errors.New("boom")}).
|
|
WithKeywordProvider(mockKeywordProvider{err: errors.New("boom")})
|
|
|
|
text := "I love good code but hate terrible bugs."
|
|
|
|
// Act
|
|
res, err := a.AnalyzeText(context.Background(), text, "en")
|
|
|
|
// Assert
|
|
require.NoError(t, err)
|
|
require.NotNil(t, res)
|
|
// Fallback sentiment should be between -1 and 1; with mixed words it should be non-zero
|
|
assert.GreaterOrEqual(t, res.Sentiment, -1.0)
|
|
assert.LessOrEqual(t, res.Sentiment, 1.0)
|
|
// Keywords should come from fallback extractor and be non-empty for this text
|
|
assert.NotEmpty(t, res.Keywords)
|
|
}
|
|
|
|
func TestAnalyzeTextConcurrently_AggregatesWithProviders(t *testing.T) {
|
|
// Providers return consistent values regardless of input
|
|
kw := []Keyword{{Text: "constant", Relevance: 0.3}}
|
|
a := NewBasicTextAnalyzer().
|
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
|
WithSentimentProvider(mockSentimentProvider{score: 0.5}).
|
|
WithKeywordProvider(mockKeywordProvider{kws: kw})
|
|
|
|
text := "One sentence. Another sentence! And a question? Final one."
|
|
|
|
// Act
|
|
_, err1 := a.AnalyzeText(context.Background(), text, "")
|
|
conc, err2 := a.AnalyzeTextConcurrently(context.Background(), text, "", 3)
|
|
|
|
// Assert
|
|
require.NoError(t, err1)
|
|
require.NoError(t, err2)
|
|
|
|
// Basic stats: should be sane
|
|
assert.Greater(t, conc.WordCount, 0)
|
|
assert.GreaterOrEqual(t, conc.SentenceCount, 0)
|
|
assert.GreaterOrEqual(t, conc.ParagraphCount, 1)
|
|
assert.GreaterOrEqual(t, conc.AvgWordLength, 0.0)
|
|
assert.GreaterOrEqual(t, conc.AvgSentenceLength, 0.0)
|
|
|
|
// Readability is clamped to [0,100]
|
|
assert.GreaterOrEqual(t, conc.ReadabilityScore, 0.0)
|
|
assert.LessOrEqual(t, conc.ReadabilityScore, 100.0)
|
|
assert.Equal(t, "Simplified Flesch-Kincaid", conc.ReadabilityMethod)
|
|
|
|
// Provider-driven outputs should align
|
|
assert.InDelta(t, 0.5, conc.Sentiment, 1e-9)
|
|
require.Len(t, conc.Keywords, 1)
|
|
assert.Equal(t, "constant", conc.Keywords[0].Text)
|
|
assert.InDelta(t, 0.3, conc.Keywords[0].Relevance, 1e-9)
|
|
}
|
|
|
|
func TestAnalyzeTextConcurrently_ContextCanceled(t *testing.T) {
|
|
a := NewBasicTextAnalyzer().
|
|
WithLanguageDetector(mockLangDetector{lang: "en", err: nil}).
|
|
WithSentimentProvider(mockSentimentProvider{score: 0.9}).
|
|
WithKeywordProvider(mockKeywordProvider{kws: []Keyword{{Text: "x", Relevance: 0.1}}})
|
|
|
|
text := "This should not be processed. Another sentence. And one more."
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel() // cancel immediately before processing
|
|
|
|
conc, err := a.AnalyzeTextConcurrently(ctx, text, "", 4)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, conc)
|
|
|
|
// With immediate cancellation, goroutines should early-return and no values should be sent
|
|
assert.Equal(t, 0, conc.WordCount)
|
|
assert.Equal(t, 0, conc.SentenceCount)
|
|
assert.Equal(t, 0, conc.ParagraphCount)
|
|
assert.Equal(t, 0.0, conc.AvgWordLength)
|
|
assert.Equal(t, 0.0, conc.AvgSentenceLength)
|
|
// Readability is clamped [0,100]; with zero stats it becomes 100
|
|
assert.GreaterOrEqual(t, conc.ReadabilityScore, 0.0)
|
|
assert.LessOrEqual(t, conc.ReadabilityScore, 100.0)
|
|
assert.Empty(t, conc.Keywords)
|
|
assert.Equal(t, 0.0, conc.Sentiment)
|
|
}
|