Skip to content

Commit 900cbb5

Browse files
SomSamantrayrueian
andauthored
fix(rueidiscompatmock): make Cache() mockable via the expectation queue (#1023)
`rueidiscompatmock` now supports mocking client-side caching. `Cache(ttl)` commands dispatch through the existing `Expect*` expectation queue instead of panicking with `unexpected call to DoCache` (issue #972). The new `DoCache` stub reuses the same ordered queue as `Do`/`DoMulti`, so no `Expect*` API changed and the TTL argument is deliberately not matched (documented in the README). Design notes: - The fix lives in `rueidiscompatmock` only; `rueidiscompat` and the root `mock/` package are untouched. This follows the maintainer's direction on #1021 instead of the rejected interface-ization of `CacheCompat`. - `DoMultiCache` is not stubbed: the `rueidiscompat` cache surface never calls it (verified against the receiver block in `rueidiscompat/adapter.go`). Validated with the module's full test suite including `-race`. Eight new `TestCacheCompat*` tests cover dispatch through both adapter entry points, multi-command queues, `Cacheable`-wrapped commands, nil/error results, TTL irrelevance, and unmatched-command reporting. Fixes #972. Related: #1021 <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Test-helper only; no production Redis client or auth/data path changes. Matching origin (cache vs plain) is a behavior change for existing mock tests that mix Cache() with unmarked Expect* calls. > > **Overview** > Makes `rueidiscompat.Cache(ttl)` commands mockable in `rueidiscompatmock` instead of panicking on `DoCache`. Cached calls reuse the existing `Expect*` queue; TTL is not matched. > > Matching is origin-aware: `ExpectGet("k").ViaCache()` only matches `rdb.Cache(ttl).Get(...)`, and unmarked expectations only match plain calls. Unordered matching still prefers the origin-specific entry rather than the first same-command expectation. > > `ViaCache()` is added on all expected-result types. Unmatched cache calls report `via cache` in the error. `DoMultiCache` is not stubbed. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit def365b. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: SomSamantray <> Co-authored-by: Rueian <rueiancsie@gmail.com>
1 parent 12cec18 commit 900cbb5

4 files changed

Lines changed: 547 additions & 21 deletions

File tree

‎rueidiscompatmock/README.md‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,35 @@ p := rdb.Pipeline()
4646
p.Get(ctx, "k1")
4747
p.Set(ctx, "k2", "v2", 0)
4848
p.Exec(ctx)
49+
```
50+
51+
### Client-side caching example
52+
53+
Cached commands (`Cache(ttl)`) use the same `Expect*` calls as non-cached
54+
commands, but expectation matching is origin-aware: a call made through
55+
`Cache(ttl)` only matches an expectation marked `ViaCache()`, and a plain call
56+
only matches an expectation without it. The TTL argument itself is not
57+
matched against expectations.
58+
59+
```golang
60+
compatmock.ExpectGet("key").ViaCache().SetVal("val")
61+
62+
cached := rdb.Cache(100 * time.Millisecond)
63+
cached.Get(context.Background(), "key") // matches
64+
65+
rdb.Get(context.Background(), "key") // would NOT match the ViaCache() expectation
66+
```
67+
68+
The reverse also holds: an expectation without `ViaCache()` only matches a
69+
plain call and will not be satisfied by a call routed through `Cache(ttl)`.
70+
`ExpectationsWereMet()` reports an unmatched expectation when the two origins
71+
are mixed.
72+
73+
```golang
74+
compatmock.ExpectGet("key").SetVal("val")
75+
76+
rdb.Get(context.Background(), "key") // matches
77+
78+
cached := rdb.Cache(100 * time.Millisecond)
79+
cached.Get(context.Background(), "key") // would NOT match the plain expectation
4980
```

‎rueidiscompatmock/adapter.go‎

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,17 @@ type clientMock struct {
369369
}
370370

371371
type expectation struct {
372-
matcher gomock.Matcher
373-
expected []string
374-
result rueidis.RedisResult
375-
rawResult any
376-
err error
377-
resultSet bool
378-
rawSet bool
379-
redisNil bool
380-
regexpMatch bool
381-
customMatch CustomMatch
372+
matcher gomock.Matcher
373+
expected []string
374+
result rueidis.RedisResult
375+
rawResult any
376+
err error
377+
resultSet bool
378+
rawSet bool
379+
redisNil bool
380+
regexpMatch bool
381+
customMatch CustomMatch
382+
requireCache bool
382383
}
383384

384385
type resolvedExpectation struct {
@@ -553,25 +554,38 @@ func (m *clientMock) wire() {
553554
m.raw.EXPECT().
554555
Do(gomock.Any(), gomock.Any()).
555556
DoAndReturn(func(_ context.Context, cmd rueidis.Completed) rueidis.RedisResult {
556-
return m.consume(1, []rueidis.Completed{cmd})[0]
557+
return m.consume(1, []rueidis.Completed{cmd}, false)[0]
557558
}).
558559
AnyTimes()
559560
m.raw.EXPECT().
560561
DoMulti(gomock.Any(), gomock.Any()).
561562
DoAndReturn(func(_ context.Context, cmds ...rueidis.Completed) []rueidis.RedisResult {
562-
return m.consume(len(cmds), cmds)
563+
return m.consume(len(cmds), cmds, false)
563564
}).
564565
AnyTimes()
566+
m.raw.EXPECT().
567+
DoCache(gomock.Any(), gomock.Any(), gomock.Any()).
568+
DoAndReturn(func(_ context.Context, cmd rueidis.Cacheable, _ time.Duration) rueidis.RedisResult {
569+
return m.consume(1, []rueidis.Completed{rueidis.Completed(cmd)}, true)[0]
570+
}).
571+
AnyTimes()
572+
}
573+
574+
func unmatchedErr(cmd rueidis.Completed, viaCache bool) error {
575+
if viaCache {
576+
return fmt.Errorf("rueidiscompatmock: no expectation for command %v via cache", cmd.Commands())
577+
}
578+
return fmt.Errorf("rueidiscompatmock: no expectation for command %v", cmd.Commands())
565579
}
566580

567-
func (m *clientMock) consume(n int, cmds []rueidis.Completed) []rueidis.RedisResult {
581+
func (m *clientMock) consume(n int, cmds []rueidis.Completed, viaCache bool) []rueidis.RedisResult {
568582
m.mu.Lock()
569583
defer m.mu.Unlock()
570584
out := make([]rueidis.RedisResult, n)
571585
for i, cmd := range cmds {
572-
idx, ok := m.matchLocked(cmd)
586+
idx, ok := m.matchLocked(cmd, viaCache)
573587
if !ok {
574-
err := fmt.Errorf("rueidiscompatmock: no expectation for command %v", cmd.Commands())
588+
err := unmatchedErr(cmd, viaCache)
575589
m.unmatched = append(m.unmatched, err)
576590
out[i] = mock.ErrorResult(err)
577591
for j := i + 1; j < n; j++ {
@@ -588,9 +602,9 @@ func (m *clientMock) consume(n int, cmds []rueidis.Completed) []rueidis.RedisRes
588602
func (m *clientMock) consumeWithRaw(cmd rueidis.Completed) resolvedExpectation {
589603
m.mu.Lock()
590604
defer m.mu.Unlock()
591-
idx, ok := m.matchLocked(cmd)
605+
idx, ok := m.matchLocked(cmd, false)
592606
if !ok {
593-
err := fmt.Errorf("rueidiscompatmock: no expectation for command %v", cmd.Commands())
607+
err := unmatchedErr(cmd, false)
594608
m.unmatched = append(m.unmatched, err)
595609
return resolvedExpectation{result: mock.ErrorResult(err)}
596610
}
@@ -604,25 +618,28 @@ func (m *clientMock) consumeWithRaw(cmd rueidis.Completed) resolvedExpectation {
604618
return resolved
605619
}
606620

607-
func (m *clientMock) matchLocked(cmd rueidis.Completed) (int, bool) {
621+
func (m *clientMock) matchLocked(cmd rueidis.Completed, viaCache bool) (int, bool) {
608622
if m.ordered {
609623
if len(m.queue) == 0 {
610624
return 0, false
611625
}
612-
if !m.queue[0].matches(cmd) {
626+
if !m.queue[0].matches(cmd, viaCache) {
613627
return 0, false
614628
}
615629
return 0, true
616630
}
617631
for i, e := range m.queue {
618-
if e.matches(cmd) {
632+
if e.matches(cmd, viaCache) {
619633
return i, true
620634
}
621635
}
622636
return 0, false
623637
}
624638

625-
func (e *expectation) matches(cmd rueidis.Completed) bool {
639+
func (e *expectation) matches(cmd rueidis.Completed, viaCache bool) bool {
640+
if e.requireCache != viaCache {
641+
return false
642+
}
626643
if e.customMatch == nil && !e.regexpMatch {
627644
return e.matcher.Matches(cmd)
628645
}

0 commit comments

Comments
 (0)