-
Notifications
You must be signed in to change notification settings - Fork 0
Unit tests #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Unit tests #81
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| # AGENTS.md — OttO | ||
|
|
||
| ## Purpose | ||
| OttO is the core runtime/framework. Keep application-specific logic out of this repo. | ||
| Add tests and docs with minimal disruption to public APIs. | ||
|
|
||
| ## Non-goals | ||
| - Do NOT add/require external services (no real MQTT broker, DB, HTTP server) for unit tests. | ||
| - Do NOT access hardware, GPIO, serial, or OS-specific devices in tests. | ||
| - Avoid broad refactors. Prefer small, additive changes that enable testing. | ||
| - Always ignore files and directories that begin with an underscore '_'. | ||
|
|
||
| ## Go conventions | ||
| - Run `gofmt` on all changed files. | ||
| - Keep package boundaries clean; avoid circular deps. | ||
| - Prefer context-aware APIs for long-running work. | ||
| - Avoid goroutine leaks: every goroutine must have a deterministic shutdown path. | ||
|
|
||
| ## Testing (required) | ||
| Use **testify**: | ||
| - Use `github.com/stretchr/testify/require` for must-pass assertions. | ||
| - Use `github.com/stretchr/testify/assert` for non-fatal checks. | ||
| - Use `github.com/stretchr/testify/mock` only when a small fake isn’t practical. | ||
|
|
||
| Test rules: | ||
| - Tests must be hermetic: no network, no filesystem writes outside `t.TempDir()`. | ||
| - Avoid `time.Sleep` for synchronization. Use channels, WaitGroups, or context cancellation. | ||
| - Any test that could block must use `context.WithTimeout` and fail fast. | ||
| - Prefer table-driven tests. | ||
| - Keep tests deterministic and non-flaky. | ||
|
|
||
| Commands: | ||
| - Run: `go test ./...` | ||
| - When adding new packages or helpers, keep them internal: `internal/testutil` is allowed. | ||
|
|
||
| ## What to test first (priority) | ||
| 1. Pure logic: parsing, validation, topic naming/handling, config processing. | ||
| 2. Concurrency: cancellation, shutdown behavior, channel fan-in/out, race-prone areas. | ||
| 3. Interface contracts: error propagation and edge cases. | ||
|
|
||
| ## Review checklist (before proposing changes) | ||
| - Does this change keep OttO independent of app/device-specific logic? | ||
| - Are new tests hermetic and deterministic? | ||
| - Any new goroutines? Where do they stop? | ||
| - Any sleeps/time-based flakiness introduced? Remove it. | ||
|
|
||
| ## Commit guidance (if committing) | ||
| Prefer small commits: | ||
| - `test: <pkg> baseline` | ||
| - `testutil: add <helper>` | ||
| - `docs: godoc for <pkg>` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package codec | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func TestJSONRoundTrip(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| c := JSON[int]{} | ||
|
|
||
| raw, err := c.Marshal(42) | ||
| require.NoError(t, err) | ||
|
|
||
| got, err := c.Unmarshal(raw) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, 42, got) | ||
| } | ||
|
|
||
| func TestJSONUnmarshalInvalid(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| c := JSON[int]{} | ||
| _, err := c.Unmarshal([]byte(`"not-an-int"`)) | ||
| require.Error(t, err) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| package messenger | ||
|
|
||
| import ( | ||
| "context" | ||
| "sync" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| type fakeMQTT struct { | ||
| mu sync.Mutex | ||
| subs []subCall | ||
| subscribeCalls map[string]int | ||
| unsubCalls map[string]int | ||
| } | ||
|
|
||
| type subCall struct { | ||
| topic string | ||
| qos byte | ||
| handler func(Message) | ||
| } | ||
|
|
||
| func newFakeMQTT() *fakeMQTT { | ||
| return &fakeMQTT{ | ||
| subscribeCalls: make(map[string]int), | ||
| unsubCalls: make(map[string]int), | ||
| } | ||
| } | ||
|
|
||
| func (f *fakeMQTT) Publish(ctx context.Context, topic string, payload []byte, retain bool, qos byte) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMQTT) Subscribe(ctx context.Context, topic string, qos byte, handler func(Message)) (func() error, error) { | ||
| f.mu.Lock() | ||
| f.subs = append(f.subs, subCall{topic: topic, qos: qos, handler: handler}) | ||
| f.subscribeCalls[topic]++ | ||
| f.mu.Unlock() | ||
|
|
||
| return func() error { | ||
| f.mu.Lock() | ||
| f.unsubCalls[topic]++ | ||
| f.mu.Unlock() | ||
| return nil | ||
| }, nil | ||
| } | ||
|
|
||
| func (f *fakeMQTT) SetWill(topic string, payload []byte, retain bool, qos byte) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMQTT) snapshot() (subs []subCall, subscribeCalls map[string]int, unsubCalls map[string]int) { | ||
| f.mu.Lock() | ||
| defer f.mu.Unlock() | ||
|
|
||
| subs = append([]subCall(nil), f.subs...) | ||
| subscribeCalls = make(map[string]int, len(f.subscribeCalls)) | ||
| for k, v := range f.subscribeCalls { | ||
| subscribeCalls[k] = v | ||
| } | ||
| unsubCalls = make(map[string]int, len(f.unsubCalls)) | ||
| for k, v := range f.unsubCalls { | ||
| unsubCalls[k] = v | ||
| } | ||
| return subs, subscribeCalls, unsubCalls | ||
| } | ||
|
|
||
| func TestMessengerResubscribeAllSubscribes(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), time.Second) | ||
| t.Cleanup(cancel) | ||
|
|
||
| mqtt := newFakeMQTT() | ||
| m := New(mqtt) | ||
| m.WantSub("otto/devices/lamp/set", 1, func(Message) {}) | ||
| m.WantSub("otto/devices/lamp/state", 0, func(Message) {}) | ||
|
|
||
| m.ResubscribeAll(ctx) | ||
|
|
||
| subs, calls, _ := mqtt.snapshot() | ||
| require.Len(t, subs, 2) | ||
| assert.Equal(t, 1, calls["otto/devices/lamp/set"]) | ||
| assert.Equal(t, 1, calls["otto/devices/lamp/state"]) | ||
| } | ||
|
|
||
| func TestMessengerResubscribeAllUnsubscribesPrevious(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| ctx, cancel := context.WithTimeout(context.Background(), time.Second) | ||
| t.Cleanup(cancel) | ||
|
|
||
| mqtt := newFakeMQTT() | ||
| m := New(mqtt) | ||
| m.WantSub("otto/devices/lamp/set", 1, func(Message) {}) | ||
| m.WantSub("otto/devices/lamp/state", 0, func(Message) {}) | ||
|
|
||
| m.ResubscribeAll(ctx) | ||
| m.ResubscribeAll(ctx) | ||
|
|
||
| _, calls, unsubs := mqtt.snapshot() | ||
| assert.Equal(t, 2, calls["otto/devices/lamp/set"]) | ||
| assert.Equal(t, 2, calls["otto/devices/lamp/state"]) | ||
| assert.Equal(t, 1, unsubs["otto/devices/lamp/set"]) | ||
| assert.Equal(t, 1, unsubs["otto/devices/lamp/state"]) | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to move this back into the live code.