Files
coder/coderd/tracing/status_writer_test.go
Mathias Fredriksson de41bd6b95 feat: add support for workspace app audit (#16801)
This change adds support for workspace app auditing.

To avoid audit log spam, we introduce the concept of app audit sessions.
An audit session is unique per workspace app, user, ip, user agent and
http status code. The sessions are stored in a separate table from audit
logs to allow use-case specific optimizations. Sessions are ephemeral
and the table does not function as a log.

The logic for auditing is placed in the DBTokenProvider for workspace
apps so that wsproxies are included.

This is the final change affecting the API fo #15139.

Updates #15139
2025-03-18 13:50:52 +02:00

150 lines
3.4 KiB
Go

package tracing_test
import (
"bufio"
"crypto/rand"
"net"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
"golang.org/x/xerrors"
"github.com/coder/coder/v2/coderd/tracing"
)
func TestStatusWriter(t *testing.T) {
t.Parallel()
t.Run("WriteHeader", func(t *testing.T) {
t.Parallel()
var (
rec = httptest.NewRecorder()
w = &tracing.StatusWriter{ResponseWriter: rec}
)
w.WriteHeader(http.StatusOK)
require.Equal(t, http.StatusOK, w.Status)
// Validate that the code is written to the underlying Response.
require.Equal(t, http.StatusOK, rec.Code)
})
t.Run("WriteHeaderTwice", func(t *testing.T) {
t.Parallel()
var (
rec = httptest.NewRecorder()
w = &tracing.StatusWriter{ResponseWriter: rec}
code = http.StatusNotFound
)
w.WriteHeader(code)
w.WriteHeader(http.StatusOK)
// Validate that we only record the first status code.
require.Equal(t, code, w.Status)
// Validate that the code is written to the underlying Response.
require.Equal(t, code, rec.Code)
})
t.Run("WriteNoHeader", func(t *testing.T) {
t.Parallel()
var (
rec = httptest.NewRecorder()
w = &tracing.StatusWriter{ResponseWriter: rec}
body = []byte("hello")
)
_, err := w.Write(body)
require.NoError(t, err)
// Should set the status to OK.
require.Equal(t, http.StatusOK, w.Status)
// We don't record the body for codes <400.
require.Equal(t, []byte(nil), w.ResponseBody())
require.Equal(t, body, rec.Body.Bytes())
})
t.Run("WriteAfterHeader", func(t *testing.T) {
t.Parallel()
var (
rec = httptest.NewRecorder()
w = &tracing.StatusWriter{ResponseWriter: rec}
body = []byte("hello")
code = http.StatusInternalServerError
)
w.WriteHeader(code)
_, err := w.Write(body)
require.NoError(t, err)
require.Equal(t, code, w.Status)
require.Equal(t, body, w.ResponseBody())
require.Equal(t, body, rec.Body.Bytes())
})
t.Run("WriteMaxBody", func(t *testing.T) {
t.Parallel()
var (
rec = httptest.NewRecorder()
w = &tracing.StatusWriter{ResponseWriter: rec}
// 8kb body.
body = make([]byte, 8<<10)
code = http.StatusInternalServerError
)
_, err := rand.Read(body)
require.NoError(t, err)
w.WriteHeader(code)
_, err = w.Write(body)
require.NoError(t, err)
require.Equal(t, code, w.Status)
require.Equal(t, body, rec.Body.Bytes())
require.Equal(t, body[:4096], w.ResponseBody())
})
t.Run("Hijack", func(t *testing.T) {
t.Parallel()
rec := httptest.NewRecorder()
w := &tracing.StatusWriter{ResponseWriter: hijacker{rec}}
_, _, err := w.Hijack()
require.Error(t, err)
require.Equal(t, "hijacked", err.Error())
})
t.Run("Middleware", func(t *testing.T) {
t.Parallel()
var (
sw *tracing.StatusWriter
rr = httptest.NewRecorder()
)
tracing.StatusWriterMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
sw = w.(*tracing.StatusWriter)
w.WriteHeader(http.StatusNoContent)
})).ServeHTTP(rr, httptest.NewRequest("GET", "/", nil))
require.Equal(t, http.StatusNoContent, rr.Code, "rr status code not set")
require.Equal(t, http.StatusNoContent, sw.Status, "sw status code not set")
})
}
type hijacker struct {
http.ResponseWriter
}
func (hijacker) Hijack() (net.Conn, *bufio.ReadWriter, error) {
return nil, nil, xerrors.New("hijacked")
}
func (h hijacker) Flush() {
if f, ok := h.ResponseWriter.(http.Flusher); ok {
f.Flush()
}
}