carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package edge
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "sync"
7 "testing"
8
9 "github.com/carlosframework/platform/internal/registry"
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestUsageMeterRecordAndDrain(t *testing.T) {
14 m := NewUsageMeter()
15 rt := registry.Route{Host: "a.example.com", App: "hello", Account: "bab"}
16 m.Record(rt, 1, 100, false)
17 m.Record(rt, 1, 50, false)
18 m.Record(registry.Route{Host: "b.example.com", App: "site"}, 1, 10, false) // Account "" -> release.OpsAccount
19
20 got := m.Drain()
21 a := got["a.example.com"]
22 if a.Requests != 2 || a.BytesOut != 150 || a.Account != "bab" || a.App != "hello" {
23 t.Fatalf("a row = %+v", a)
24 }
25 if got["b.example.com"].Account != release.OpsAccount {
26 t.Fatalf("b row = %+v", got["b.example.com"])
27 }
28 if again := m.Drain(); len(again) != 0 {
29 t.Fatalf("drain did not reset: %+v", again)
30 }
31}
32
33// A hopped exchange is recorded ONCE — into the totals, as any exchange is —
34// and additionally annotated into the hop counters, which are therefore a
35// subset of the totals and never an addition to them. A consumer that sums
36// BytesOut has already counted every backhauled byte; one that added
37// HopBytesOut on top would bill the tenant twice for the same wire bytes.
38func TestRecordHopAnnotatesSubset(t *testing.T) {
39 m := NewUsageMeter()
40 rt := registry.Route{Host: "a.example.com", App: "hello", Account: "bab"}
41 m.Record(rt, 1, 100, true)
42 m.Record(rt, 1, 50, false)
43
44 row := m.Drain()["a.example.com"]
45 if row.Requests != 2 || row.BytesOut != 150 {
46 t.Fatalf("row = %+v, want both exchanges counted once in the totals", row)
47 }
48 if row.HopRequests != 1 || row.HopBytesOut != 100 {
49 t.Fatalf("row = %+v, want only the hopped exchange annotated", row)
50 }
51 if row.HopRequests > row.Requests || row.HopBytesOut > row.BytesOut {
52 t.Fatalf("row = %+v — the hop counters must stay inside the totals", row)
53 }
54 if row.Account != "bab" || row.App != "hello" {
55 t.Fatalf("row = %+v, want the hop attributed to the route's own account and app", row)
56 }
57}
58
59func TestUsageMeterConcurrent(t *testing.T) {
60 m := NewUsageMeter()
61 rt := registry.Route{Host: "a.example.com", App: "hello", Account: "bab"}
62 var wg sync.WaitGroup
63 for i := 0; i < 50; i++ {
64 wg.Add(1)
65 go func() {
66 defer wg.Done()
67 for j := 0; j < 100; j++ {
68 m.Record(rt, 1, 10, false)
69 }
70 }()
71 }
72 wg.Wait()
73 row := m.Drain()["a.example.com"]
74 if row.Requests != 5000 || row.BytesOut != 50000 {
75 t.Fatalf("row = %+v", row)
76 }
77}
78
79func TestCountingWriterCountsBytes(t *testing.T) {
80 var n int64
81 rec := httptest.NewRecorder()
82 w := &countingWriter{ResponseWriter: rec, n: &n}
83 w.WriteHeader(http.StatusTeapot)
84 w.Write([]byte("hello "))
85 w.Write([]byte("world"))
86 if n != 11 {
87 t.Fatalf("counted %d bytes", n)
88 }
89 if rec.Code != http.StatusTeapot || rec.Body.String() != "hello world" {
90 t.Fatalf("forwarding broken: %d %q", rec.Code, rec.Body.String())
91 }
92}
93
94func TestCountingWriterObservesStatusAndType(t *testing.T) {
95 var n int64
96 rec := httptest.NewRecorder()
97 w := &countingWriter{ResponseWriter: rec, n: &n}
98 w.Header().Set("Content-Type", "text/html; charset=utf-8")
99 w.WriteHeader(103) // informational: forwarded, not latched
100 w.WriteHeader(http.StatusBadGateway)
101 w.WriteHeader(http.StatusOK) // superfluous second final status: ignored
102 if w.status != http.StatusBadGateway || w.ctype != "text/html; charset=utf-8" {
103 t.Fatalf("latched %d %q", w.status, w.ctype)
104 }
105 // Implicit 200 at first Write.
106 rec2 := httptest.NewRecorder()
107 w2 := &countingWriter{ResponseWriter: rec2, n: &n}
108 w2.Write([]byte("x"))
109 if w2.status != http.StatusOK {
110 t.Fatalf("implicit status = %d", w2.status)
111 }
112 // Nothing written at all: status stays 0 (the caller treats it as a
113 // bodiless 200 that is never a pageview and never an error).
114 w3 := &countingWriter{ResponseWriter: httptest.NewRecorder(), n: &n}
115 if w3.status != 0 {
116 t.Fatal("untouched writer must report 0")
117 }
118}
119