Skip to content

Commit 226d095

Browse files
committed
test: replace MongoDB dependency with mocks and containerized tests
Replace ENABLE_MONGO_TESTS-gated tests with self-contained alternatives: rest package uses moq-generated RulesMock with in-memory CRUD closures, datastore package uses go-pkgz/testutils containerized MongoDB. Add text_test.go and TestExtractByRule for extractor coverage. Coverage: rest 20%->89%, datastore 68%->95%, extractor 87%->91%.
1 parent 1b28637 commit 226d095

1,951 files changed

Lines changed: 436192 additions & 35502 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/datastore/mongo_test.go

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,54 @@
11
package datastore
22

33
import (
4-
"os"
4+
"context"
55
"testing"
66
"time"
77

8+
"github.com/go-pkgz/testutils/containers"
89
"github.com/stretchr/testify/assert"
910
"github.com/stretchr/testify/require"
1011
)
1112

12-
func TestMongoCreation(t *testing.T) {
13-
if _, ok := os.LookupEnv("ENABLE_MONGO_TESTS"); !ok {
14-
t.Skip("ENABLE_MONGO_TESTS env variable is not set")
15-
}
16-
// wrong credentials, so that GetStores will fail with warning
17-
server, err := New("mongodb://wrong:wrong@localhost:27017/", "test_ureadability", 0)
18-
require.NoError(t, err)
19-
assert.NotNil(t, server)
20-
assert.NotNil(t, server.client)
21-
assert.Equal(t, "test_ureadability", server.dbName)
22-
assert.NotNil(t, server.GetStores())
13+
func TestNew(t *testing.T) {
14+
mc := containers.NewMongoTestContainer(context.Background(), t, 5)
15+
defer mc.Close(context.Background()) //nolint:errcheck
16+
17+
t.Run("valid connection", func(t *testing.T) {
18+
server, err := New(mc.URI, "test_ureadability", 0)
19+
require.NoError(t, err)
20+
assert.NotNil(t, server)
21+
assert.NotNil(t, server.client)
22+
assert.Equal(t, "test_ureadability", server.dbName)
23+
})
24+
25+
t.Run("with delay", func(t *testing.T) {
26+
server, err := New(mc.URI, "test_ureadability", 10*time.Millisecond)
27+
require.NoError(t, err)
28+
assert.NotNil(t, server)
29+
})
30+
31+
t.Run("wrong connection string", func(t *testing.T) {
32+
server, err := New("wrong", "test_ureadability", 0)
33+
require.Error(t, err)
34+
assert.Nil(t, server)
35+
})
36+
37+
t.Run("empty connection string", func(t *testing.T) {
38+
server, err := New("", "", 0)
39+
require.Error(t, err)
40+
assert.Nil(t, server)
41+
})
2342
}
2443

25-
func TestWrongConnectionString(t *testing.T) {
26-
server, err := New("wrong", "test_ureadability", time.Millisecond*100)
27-
require.Error(t, err)
28-
assert.Nil(t, server)
29-
server, err = New("", "", time.Millisecond*100)
30-
require.Error(t, err)
31-
assert.Nil(t, server)
44+
func TestGetStores(t *testing.T) {
45+
mc := containers.NewMongoTestContainer(context.Background(), t, 5)
46+
defer mc.Close(context.Background()) //nolint:errcheck
47+
48+
server, err := New(mc.URI, "test_ureadability", 0)
49+
require.NoError(t, err)
50+
51+
stores := server.GetStores()
52+
assert.NotNil(t, stores.Rules)
53+
assert.NotNil(t, stores.Rules.Collection)
3254
}

backend/datastore/rules_test.go

Lines changed: 210 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,110 +3,233 @@ package datastore
33
import (
44
"context"
55
"math/rand/v2"
6-
"os"
76
"testing"
87

8+
"github.com/go-pkgz/testutils/containers"
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11+
"go.mongodb.org/mongo-driver/bson/primitive"
1112
)
1213

1314
const letterBytes = "abcdefghijklmnopqrstuvwxyz"
1415

15-
func TestRules(t *testing.T) {
16-
if _, ok := os.LookupEnv("ENABLE_MONGO_TESTS"); !ok {
17-
t.Skip("ENABLE_MONGO_TESTS env variable is not set")
18-
}
19-
server, err := New("mongodb://localhost:27017/", "test_ureadability", 0)
20-
require.NoError(t, err)
21-
assert.NotNil(t, server.client)
22-
stores := server.GetStores()
23-
assert.NotNil(t, stores)
24-
rules := stores.Rules
25-
assert.NotNil(t, rules)
16+
func TestRulesSave(t *testing.T) {
17+
rules := setupRules(t)
18+
19+
t.Run("save new rule", func(t *testing.T) {
20+
rule := Rule{Domain: randDomain(), Content: "article p", Enabled: true}
21+
saved, err := rules.Save(context.Background(), rule)
22+
require.NoError(t, err)
23+
assert.Equal(t, rule.Domain, saved.Domain)
24+
assert.Equal(t, rule.Content, saved.Content)
25+
assert.True(t, saved.Enabled)
26+
assert.NotEqual(t, primitive.NilObjectID, saved.ID)
27+
})
28+
29+
t.Run("upsert same domain preserves id", func(t *testing.T) {
30+
domain := randDomain()
31+
rule := Rule{Domain: domain, Content: "original", Enabled: true}
32+
saved, err := rules.Save(context.Background(), rule)
33+
require.NoError(t, err)
34+
origID := saved.ID
35+
36+
updated := Rule{Domain: domain, Content: "updated", Enabled: true}
37+
saved, err = rules.Save(context.Background(), updated)
38+
require.NoError(t, err)
39+
assert.Equal(t, origID, saved.ID)
40+
assert.Equal(t, "updated", saved.Content)
41+
})
42+
43+
t.Run("save with all fields", func(t *testing.T) {
44+
rule := Rule{
45+
Domain: randDomain(),
46+
Content: ".post-content",
47+
Author: "test-author",
48+
MatchURLs: []string{"/blog/*"},
49+
Excludes: []string{".sidebar"},
50+
TestURLs: []string{"https://example.com/test"},
51+
Enabled: true,
52+
}
53+
saved, err := rules.Save(context.Background(), rule)
54+
require.NoError(t, err)
55+
assert.Equal(t, rule.Domain, saved.Domain)
56+
assert.Equal(t, rule.Author, saved.Author)
57+
assert.Equal(t, rule.MatchURLs, saved.MatchURLs)
58+
assert.Equal(t, rule.Excludes, saved.Excludes)
59+
assert.Equal(t, rule.TestURLs, saved.TestURLs)
60+
})
61+
62+
t.Run("save with canceled context", func(t *testing.T) {
63+
ctx, cancel := context.WithCancel(context.Background())
64+
cancel()
65+
rule := Rule{Domain: "example.com", Enabled: true}
66+
_, err := rules.Save(ctx, rule)
67+
require.Error(t, err)
68+
})
69+
}
70+
71+
func TestRulesGet(t *testing.T) {
72+
rules := setupRules(t)
73+
74+
t.Run("get existing enabled rule by url", func(t *testing.T) {
75+
domain := randDomain()
76+
rule := Rule{Domain: domain, Content: "article", Enabled: true}
77+
_, err := rules.Save(context.Background(), rule)
78+
require.NoError(t, err)
79+
80+
found, ok := rules.Get(context.Background(), "https://"+domain+"/some/path")
81+
assert.True(t, ok)
82+
assert.Equal(t, domain, found.Domain)
83+
assert.Equal(t, "article", found.Content)
84+
})
85+
86+
t.Run("disabled rule not found", func(t *testing.T) {
87+
domain := randDomain()
88+
rule := Rule{Domain: domain, Content: "article", Enabled: true}
89+
saved, err := rules.Save(context.Background(), rule)
90+
require.NoError(t, err)
91+
err = rules.Disable(context.Background(), saved.ID)
92+
require.NoError(t, err)
93+
94+
_, ok := rules.Get(context.Background(), "https://"+domain+"/page")
95+
assert.False(t, ok)
96+
})
97+
98+
t.Run("non-existing domain", func(t *testing.T) {
99+
found, ok := rules.Get(context.Background(), "https://nonexistent-domain-xyz.com/page")
100+
assert.False(t, ok)
101+
assert.Empty(t, found.Domain)
102+
})
103+
104+
t.Run("invalid url", func(t *testing.T) {
105+
found, ok := rules.Get(context.Background(), "http://user^:passwo^rd@foo.com/")
106+
assert.False(t, ok)
107+
assert.Empty(t, found)
108+
})
109+
110+
t.Run("canceled context", func(t *testing.T) {
111+
ctx, cancel := context.WithCancel(context.Background())
112+
cancel()
113+
_, ok := rules.Get(ctx, "https://example.com")
114+
assert.False(t, ok)
115+
})
116+
}
117+
118+
func TestRulesGetByID(t *testing.T) {
119+
rules := setupRules(t)
120+
121+
t.Run("existing rule", func(t *testing.T) {
122+
rule := Rule{Domain: randDomain(), Content: "article", Enabled: true}
123+
saved, err := rules.Save(context.Background(), rule)
124+
require.NoError(t, err)
125+
126+
found, ok := rules.GetByID(context.Background(), saved.ID)
127+
assert.True(t, ok)
128+
assert.Equal(t, saved.ID, found.ID)
129+
assert.Equal(t, rule.Domain, found.Domain)
130+
})
131+
132+
t.Run("non-existing id", func(t *testing.T) {
133+
_, ok := rules.GetByID(context.Background(), primitive.NewObjectID())
134+
assert.False(t, ok)
135+
})
136+
137+
t.Run("nil object id", func(t *testing.T) {
138+
_, ok := rules.GetByID(context.Background(), primitive.NilObjectID)
139+
assert.False(t, ok)
140+
})
141+
}
142+
143+
func TestRulesDisable(t *testing.T) {
144+
rules := setupRules(t)
145+
146+
t.Run("disable existing rule", func(t *testing.T) {
147+
rule := Rule{Domain: randDomain(), Enabled: true}
148+
saved, err := rules.Save(context.Background(), rule)
149+
require.NoError(t, err)
150+
assert.True(t, saved.Enabled)
151+
152+
err = rules.Disable(context.Background(), saved.ID)
153+
require.NoError(t, err)
154+
155+
found, ok := rules.GetByID(context.Background(), saved.ID)
156+
assert.True(t, ok)
157+
assert.False(t, found.Enabled)
158+
})
159+
160+
t.Run("disable non-existing id does not error", func(t *testing.T) {
161+
err := rules.Disable(context.Background(), primitive.NewObjectID())
162+
require.NoError(t, err) // mongo UpdateOne with no match is not an error
163+
})
164+
}
165+
166+
func TestRulesAll(t *testing.T) {
167+
rules := setupRules(t)
168+
169+
t.Run("returns all rules including disabled", func(t *testing.T) {
170+
domain1 := randDomain()
171+
domain2 := randDomain()
172+
_, err := rules.Save(context.Background(), Rule{Domain: domain1, Content: "a", Enabled: true})
173+
require.NoError(t, err)
174+
saved2, err := rules.Save(context.Background(), Rule{Domain: domain2, Content: "b", Enabled: true})
175+
require.NoError(t, err)
176+
err = rules.Disable(context.Background(), saved2.ID)
177+
require.NoError(t, err)
178+
179+
all := rules.All(context.Background())
180+
assert.GreaterOrEqual(t, len(all), 2)
181+
182+
var foundEnabled, foundDisabled bool
183+
for _, r := range all {
184+
if r.Domain == domain1 {
185+
foundEnabled = true
186+
assert.True(t, r.Enabled)
187+
}
188+
if r.Domain == domain2 {
189+
foundDisabled = true
190+
assert.False(t, r.Enabled)
191+
}
192+
}
193+
assert.True(t, foundEnabled, "enabled rule should be in All()")
194+
assert.True(t, foundDisabled, "disabled rule should be in All()")
195+
})
196+
197+
t.Run("canceled context returns empty", func(t *testing.T) {
198+
ctx, cancel := context.WithCancel(context.Background())
199+
cancel()
200+
all := rules.All(ctx)
201+
assert.Empty(t, all)
202+
})
203+
}
204+
205+
func TestRuleString(t *testing.T) {
26206
rule := Rule{
27-
Domain: randStringBytesRmndr(42) + ".com",
207+
ID: primitive.NewObjectID(),
208+
Domain: "example.com",
209+
Content: ".article",
28210
Enabled: true,
29211
}
30-
31-
// save a rule
32-
srule, err := rules.Save(context.Background(), rule)
33-
require.NoError(t, err)
34-
assert.Equal(t, rule.Domain, srule.Domain)
35-
ruleID := srule.ID
36-
37-
// get the rule we just saved by domain
38-
grule, found := rules.Get(context.Background(), "https://"+rule.Domain)
39-
assert.True(t, found)
40-
assert.Equal(t, rule.Domain, grule.Domain)
41-
assert.Equal(t, ruleID, grule.ID)
42-
assert.Contains(t, rules.All(context.Background()), grule)
43-
44-
// get the rule by ID
45-
idrule, found := rules.GetByID(context.Background(), ruleID)
46-
assert.True(t, found)
47-
assert.Equal(t, grule, idrule)
48-
49-
// disable the rule
50-
err = rules.Disable(context.Background(), grule.ID)
51-
require.NoError(t, err)
52-
assert.NotContains(t, rules.All(context.Background()), grule)
53-
54-
// get the rule by ID, should be marked as disabled
55-
idrule, found = rules.GetByID(context.Background(), grule.ID)
56-
assert.True(t, found)
57-
assert.Equal(t, rule.Domain, grule.Domain)
58-
assert.False(t, idrule.Enabled)
59-
// same disabled rule still should appear in All call
60-
assert.Contains(t, rules.All(context.Background()), idrule)
61-
62-
// get the disabled rule by domain, should not be found
63-
grule, found = rules.Get(context.Background(), "https://"+rule.Domain)
64-
assert.False(t, found)
65-
assert.Empty(t, grule.Domain)
66-
67-
// save a rule once more, should result in the same ID
68-
updatedRule, err := rules.Save(context.Background(), rule)
69-
require.NoError(t, err)
70-
assert.Equal(t, rule.Domain, updatedRule.Domain)
71-
assert.Equal(t, ruleID, updatedRule.ID)
212+
s := rule.String()
213+
assert.Contains(t, s, "example.com")
214+
assert.Contains(t, s, ".article")
215+
assert.Contains(t, s, "enabled=true")
72216
}
73217

74-
func TestRulesCanceledContext(t *testing.T) {
75-
// we're not making requests to MongoDB, so it's ok to have no working connection
76-
server, err := New("mongodb://wrong", "", 0)
218+
func setupRules(t *testing.T) RulesDAO {
219+
t.Helper()
220+
mc := containers.NewMongoTestContainer(context.Background(), t, 5)
221+
t.Cleanup(func() { mc.Close(context.Background()) }) //nolint:errcheck
222+
223+
server, err := New(mc.URI, "test_ureadability", 0)
77224
require.NoError(t, err)
78-
assert.NotNil(t, server.client)
79225
stores := server.GetStores()
80-
assert.NotNil(t, stores)
81-
rules := stores.Rules
82-
assert.NotNil(t, rules)
83-
84-
ctx, cancel := context.WithCancel(context.Background())
85-
cancel()
86-
87-
// save a rule with canceled context
88-
rule := Rule{Domain: "example.com", Enabled: true}
89-
srule, err := rules.Save(ctx, rule)
90-
assert.Equal(t, rule, srule)
91-
require.Error(t, err)
92-
93-
// retrieve a rule, wrong rule
94-
grule, found := rules.Get(context.Background(), "http://user^:passwo^rd@foo.com/")
95-
assert.Empty(t, grule, "wrong URL")
96-
assert.False(t, found, "wrong URL")
97-
// retrieve a rule with canceled context
98-
grule, found = rules.Get(ctx, "")
99-
assert.Empty(t, grule, "canceled context")
100-
assert.False(t, found, "canceled context")
101-
assert.Empty(t, rules.All(ctx))
102-
require.Error(t, rules.Disable(ctx, rule.ID))
103-
// get a rule by ID with canceled context
104-
grule, found = rules.GetByID(ctx, rule.ID)
105-
assert.Empty(t, grule)
106-
assert.False(t, found)
226+
return stores.Rules
227+
}
228+
229+
func randDomain() string {
230+
return randStringBytesRmndr(20) + ".com"
107231
}
108232

109-
// thanks to https://stackoverflow.com/a/31832326/961092
110233
func randStringBytesRmndr(n int) string {
111234
b := make([]byte, n)
112235
for i := range b {

0 commit comments

Comments
 (0)