fb42202834
The router.SecureProvider interface previously required a public IsSecure() bool method. Any test mock — or future production type — could satisfy it by returning true, defeating the W1 "only wrapped providers may flow past the boundary" contract through convention rather than at the type level. Replaces IsSecure() bool with an unexported security.Marker interface that has a single secured() method. Go's method-set semantics key unexported methods by their defining package, so only types declared in internal/security can satisfy Marker. *SafeProvider gets the lone secured() implementation; router.SecureProvider embeds Marker. The seal forces every test mock that previously implemented IsSecure() to either (a) be wrapped with security.WrapProvider(mp, nil) at the use site, or (b) drop the method entirely if the mock never flows through SecureProvider. 93 use sites across 11 test files were updated via a per-package secureMock helper. WrapProvider with a nil firewall ref is a no-op pass-through, so test behavior is unchanged. Empirically: a type from outside internal/security can declare `secured()` but the compiler will reject assigning it to router.SecureProvider because the unexported method belongs to the other package's namespace. Convention → compile-time guarantee.
78 lines
1.8 KiB
Go
78 lines
1.8 KiB
Go
package engine
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"testing"
|
|
|
|
"somegit.dev/Owlibou/gnoma/internal/message"
|
|
"somegit.dev/Owlibou/gnoma/internal/stream"
|
|
"somegit.dev/Owlibou/gnoma/internal/tool"
|
|
)
|
|
|
|
// blockingStream emits one text delta, then blocks Next() until release is closed,
|
|
// then emits the stop event. Lets a test interleave Submit with concurrent setters.
|
|
type blockingStream struct {
|
|
release chan struct{}
|
|
emitted bool
|
|
released bool
|
|
stopReason message.StopReason
|
|
model string
|
|
}
|
|
|
|
func newBlockingStream(release chan struct{}, model string) *blockingStream {
|
|
return &blockingStream{release: release, model: model, stopReason: message.StopEndTurn}
|
|
}
|
|
|
|
func (s *blockingStream) Next() bool {
|
|
if !s.emitted {
|
|
s.emitted = true
|
|
return true
|
|
}
|
|
if !s.released {
|
|
<-s.release
|
|
s.released = true
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *blockingStream) Current() stream.Event {
|
|
if s.released {
|
|
return stream.Event{Type: stream.EventTextDelta, StopReason: s.stopReason, Model: s.model}
|
|
}
|
|
return stream.Event{Type: stream.EventTextDelta, Text: "hi", Model: s.model}
|
|
}
|
|
|
|
func (s *blockingStream) Err() error { return nil }
|
|
func (s *blockingStream) Close() error { return nil }
|
|
|
|
func TestEngine_ConcurrentSubmitAndSetters(t *testing.T) {
|
|
release := make(chan struct{})
|
|
mp := &mockProvider{
|
|
name: "test",
|
|
streams: []stream.Stream{newBlockingStream(release, "mock-model")},
|
|
}
|
|
e, _ := New(Config{Provider: secureMock(mp), Tools: tool.NewRegistry()})
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(2)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
_, _ = e.Submit(context.Background(), "go", nil)
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
for i := 0; i < 100; i++ {
|
|
e.InjectMessage(message.NewUserText("noise"))
|
|
_ = e.History()
|
|
_ = e.Usage()
|
|
}
|
|
close(release)
|
|
}()
|
|
|
|
wg.Wait()
|
|
}
|