package servertiming import ( "net/http" "reflect" "testing" "time" ) // Example taken from the W3C spec // see: https://w3c.github.io/server-timing/#examples // // ``` // > GET /resource HTTP/1.1 // > Host: example.com // // // < HTTP/1.1 200 OK // < Server-Timing: miss, db;dur=53, app;dur=47.2 // < Server-Timing: customView, dc;desc=atl // < Server-Timing: cache;desc="Cache Read";dur=23.2 // < Trailer: Server-Timing // < (... snip response body ...) // < Server-Timing: total;dur=123.4 // ``` // // | Name | Duration | Description | // | ---------- | -------- | ----------- | // | miss | | | // | db | 53 | | // | app | 47.2 | | // | customView | | | // | dc | | atl | // | cache | 23.2 | Cache Read | // | total | 123.4 | | func TestServerTiming_String(t *testing.T) { tests := []struct { name string st ServerTiming want string }{ { name: "just name", st: ServerTiming{Name: "miss"}, want: "miss", }, { name: "name and dur", st: ServerTiming{ Name: "db", Dur: 53 * time.Millisecond, }, want: "db;dur=53", }, { name: "name and decimal dur", st: ServerTiming{ Name: "app", Dur: 47_200 * time.Microsecond, DecimalPrecision: 1, }, want: "app;dur=47.2", }, { name: "name and nanosecond dur", st: ServerTiming{ Name: "app", Dur: 47_200 * time.Microsecond, DecimalPrecision: 6, }, want: "app;dur=47.200000", }, { name: "name and dur, negative precision", st: ServerTiming{ Name: "app", Dur: 47_200 * time.Microsecond, DecimalPrecision: -1, }, want: "app;dur=47", }, { name: "name and dur, out-of-bound precision", st: ServerTiming{ Name: "app", Dur: 47_200 * time.Microsecond, DecimalPrecision: 7, }, want: "app;dur=47.200000", }, { name: "name and desc", st: ServerTiming{ Name: "dc", Desc: "atl", }, want: "dc;desc=atl", }, { name: "name, desc, and dur", st: ServerTiming{ Name: "cache", Dur: 23 * time.Millisecond, Desc: "Cache Read", }, want: "cache;dur=23;desc=Cache Read", }, { name: "name, desc, and decimal dur", st: ServerTiming{ Name: "cache", Dur: 23_200 * time.Microsecond, Desc: "Cache Read", DecimalPrecision: 1, }, want: "cache;dur=23.2;desc=Cache Read", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := tt.st.String(); got != tt.want { t.Errorf("ServerTiming.String() = %v, want %v", got, tt.want) } }) } } func TestFromString(t *testing.T) { tests := []struct { name string s string want ServerTiming }{ { name: "empty", s: "", want: ServerTiming{}, }, { name: "name only", s: "miss", want: ServerTiming{Name: "miss"}, }, { name: "name and dur", s: "db;dur=53", want: ServerTiming{ Name: "db", Dur: 53 * time.Millisecond, }, }, { name: "name, dur and desc", s: "cache;dur=23;desc=Cache Read", want: ServerTiming{ Name: "cache", Dur: 23 * time.Millisecond, Desc: "Cache Read", }, }, { name: "name, desc", s: "cache;desc=Cache Read;dur=23", want: ServerTiming{ Name: "cache", Dur: 23 * time.Millisecond, Desc: "Cache Read", }, }, { name: "name, dur and desc with padding", s: "cache ; dur=23 ; desc=Cache Read ", want: ServerTiming{ Name: "cache", Dur: 23 * time.Millisecond, Desc: "Cache Read", }, }, { name: "name and decimal dur", s: "cache;dur=23.2", want: ServerTiming{ Name: "cache", Dur: 23_200 * time.Microsecond, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := FromString(tt.s); !reflect.DeepEqual(got, tt.want) { t.Errorf("FromString() = %v, want %v", got, tt.want) } }) } } func TestAppend(t *testing.T) { resp := &http.Response{ Header: http.Header{}, } tests := []struct { name string st ServerTiming }{ { name: "name only", st: ServerTiming{Name: "miss"}, }, { name: "name and dur", st: ServerTiming{ Name: "db", Dur: 53 * time.Millisecond, }, }, { name: "name, dur and desc", st: ServerTiming{ Name: "cache", Dur: 23 * time.Millisecond, Desc: "Cache Read", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { Append(resp, tt.st) }) } vals := resp.Header.Values("Server-Timing") if len(vals) != len(tests) { t.Errorf("Expected %d values in the headers, got %d", len(tests), len(vals)) } for i, v := range vals { if v != tests[i].st.String() { t.Errorf("Expected '%s', got %s", tests[i].st.String(), v) } } } func TestTrailer(t *testing.T) { type args struct { r *http.Response t ServerTiming } tests := []struct { name string args args }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { Trailer(tt.args.r, tt.args.t) }) } } func TestParse(t *testing.T) { resp := &http.Response{ Header: http.Header{}, Trailer: http.Header{}, } resp.Header.Add("Server-Timing", "miss, db;dur=53, app;dur=47") resp.Header.Add("Server-Timing", "customView, dc;desc=atl") resp.Header.Add("Server-Timing", `cache;desc="Cache Read";dur=23`) resp.Trailer.Add("Server-Timing", "total;dur=123") timings := Parse(resp) if len(timings) != 7 { t.Errorf("Expected 7 timings, got %d", len(timings)) } } func TestParseWithDecimal(t *testing.T) { resp := &http.Response{ Header: http.Header{}, Trailer: http.Header{}, } resp.Header.Add("Server-Timing", "miss, db;dur=53, app;dur=47.2") resp.Header.Add("Server-Timing", "customView, dc;desc=atl") resp.Header.Add("Server-Timing", `cache;desc="Cache Read";dur=23.2`) resp.Trailer.Add("Server-Timing", "total;dur=123.4") timings := Parse(resp) if len(timings) != 7 { t.Errorf("Expected 7 timings, got %d", len(timings)) } }