Files
docker-agent/pkg/app/queue_session_test.go
David Gageot 4c1f873503 fix(tui): scope asynchronous input results to their originating page
Captures app/session/routing identity at dispatch time and routes both
success and failure back to the owner page only. Page-lifetime scope
rejects results from replaced or closed pages; scope is cancelled on
cleanup and best-effort withdraws unacknowledged runtime submissions.
Explicit session snapshots for attachment handling avoid mutable
app.session reads.

Cancellation/drop on a retired page is an intentional correctness
change, not a behaviour-preserving refactor.

Assisted-By: docker-agent
2026-09-14 13:13:29 +02:00

81 lines
2.3 KiB
Go

package app
import (
"context"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/docker/docker-agent/pkg/runtime"
"github.com/docker/docker-agent/pkg/session"
"github.com/docker/docker-agent/pkg/tui/messages"
)
type queueSessionRuntime struct {
mockRuntime
submitted []runtime.QueuedMessage
}
func (r *queueSessionRuntime) Steer(_ context.Context, msg runtime.QueuedMessage) error {
r.submitted = append(r.submitted, msg)
return nil
}
func (r *queueSessionRuntime) FollowUp(_ context.Context, msg runtime.QueuedMessage) error {
r.submitted = append(r.submitted, msg)
return nil
}
func TestQueueMessageUsesSessionSnapshot(t *testing.T) {
t.Parallel()
for _, follow := range []bool{false, true} {
t.Run(map[bool]string{false: "steer", true: "follow-up"}[follow], func(t *testing.T) {
t.Parallel()
rt := &queueSessionRuntime{}
original := session.New()
a := New(t.Context(), rt, original)
enqueue := a.QueueSteerMessageForSession
if follow {
enqueue = a.QueueFollowUpMessageForSession
}
replacement := session.New()
a.ReplaceSession(t.Context(), replacement)
file := filepath.Join(t.TempDir(), "note.txt")
require.NoError(t, os.WriteFile(file, []byte("original attachment"), 0o600))
submitted, err := enqueue(t.Context(), original, "hello", []messages.Attachment{{FilePath: file}})
require.NoError(t, err)
assert.Equal(t, []string{file}, original.AttachedFilesSnapshot())
assert.Empty(t, replacement.AttachedFilesSnapshot())
require.Len(t, rt.submitted, 1)
assert.Equal(t, submitted, rt.submitted[0])
assert.NotEmpty(t, submitted.ID)
assert.NotEmpty(t, submitted.MultiContent)
})
}
}
func TestQueueMessageRejectsCancelledSubmission(t *testing.T) {
t.Parallel()
for _, follow := range []bool{false, true} {
t.Run(map[bool]string{false: "steer", true: "follow-up"}[follow], func(t *testing.T) {
t.Parallel()
rt := &queueSessionRuntime{}
sess := session.New()
a := New(t.Context(), rt, sess)
enqueue := a.QueueSteerMessageForSession
if follow {
enqueue = a.QueueFollowUpMessageForSession
}
ctx, cancel := context.WithCancel(t.Context())
cancel()
_, err := enqueue(ctx, sess, "cancelled", nil)
require.ErrorIs(t, err, context.Canceled)
assert.Empty(t, rt.submitted)
})
}
}