server_timing_sql_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package repository
  2. import (
  3. "context"
  4. "database/sql/driver"
  5. "io"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/Wei-Shaw/sub2api/internal/pkg/servertiming"
  12. )
  13. const fakeDriverDelay = 2 * time.Millisecond
  14. type timingFakeDriver struct{}
  15. func (timingFakeDriver) Open(string) (driver.Conn, error) { return newTimingFakeConn(), nil }
  16. type timingFakeConnector struct {
  17. conn driver.Conn
  18. }
  19. func (c timingFakeConnector) Connect(context.Context) (driver.Conn, error) {
  20. time.Sleep(fakeDriverDelay)
  21. return c.conn, nil
  22. }
  23. func (timingFakeConnector) Driver() driver.Driver { return timingFakeDriver{} }
  24. type timingFakeConn struct{}
  25. func newTimingFakeConn() *timingFakeConn { return &timingFakeConn{} }
  26. func (c *timingFakeConn) Prepare(string) (driver.Stmt, error) {
  27. time.Sleep(fakeDriverDelay)
  28. return &timingFakeStmt{}, nil
  29. }
  30. func (c *timingFakeConn) PrepareContext(context.Context, string) (driver.Stmt, error) {
  31. time.Sleep(fakeDriverDelay)
  32. return &timingFakeStmt{}, nil
  33. }
  34. func (c *timingFakeConn) Close() error { return nil }
  35. func (c *timingFakeConn) Begin() (driver.Tx, error) {
  36. time.Sleep(fakeDriverDelay)
  37. return &timingFakeTx{}, nil
  38. }
  39. func (c *timingFakeConn) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) {
  40. time.Sleep(fakeDriverDelay)
  41. return &timingFakeTx{}, nil
  42. }
  43. func (c *timingFakeConn) ExecContext(context.Context, string, []driver.NamedValue) (driver.Result, error) {
  44. time.Sleep(fakeDriverDelay)
  45. return driver.RowsAffected(1), nil
  46. }
  47. func (c *timingFakeConn) QueryContext(context.Context, string, []driver.NamedValue) (driver.Rows, error) {
  48. time.Sleep(fakeDriverDelay)
  49. return &timingFakeRows{values: [][]driver.Value{{"value"}}}, nil
  50. }
  51. func (c *timingFakeConn) Ping(context.Context) error {
  52. time.Sleep(fakeDriverDelay)
  53. return nil
  54. }
  55. func (c *timingFakeConn) ResetSession(context.Context) error {
  56. time.Sleep(fakeDriverDelay)
  57. return nil
  58. }
  59. type timingFakeStmt struct{}
  60. func (s *timingFakeStmt) Close() error { return nil }
  61. func (s *timingFakeStmt) NumInput() int { return -1 }
  62. func (s *timingFakeStmt) Exec([]driver.Value) (driver.Result, error) {
  63. time.Sleep(fakeDriverDelay)
  64. return driver.RowsAffected(1), nil
  65. }
  66. func (s *timingFakeStmt) Query([]driver.Value) (driver.Rows, error) {
  67. time.Sleep(fakeDriverDelay)
  68. return &timingFakeRows{values: [][]driver.Value{{"value"}}}, nil
  69. }
  70. func (s *timingFakeStmt) ExecContext(context.Context, []driver.NamedValue) (driver.Result, error) {
  71. time.Sleep(fakeDriverDelay)
  72. return driver.RowsAffected(1), nil
  73. }
  74. func (s *timingFakeStmt) QueryContext(context.Context, []driver.NamedValue) (driver.Rows, error) {
  75. time.Sleep(fakeDriverDelay)
  76. return &timingFakeRows{values: [][]driver.Value{{"value"}}}, nil
  77. }
  78. type timingFakeRows struct {
  79. values [][]driver.Value
  80. index int
  81. }
  82. func (r *timingFakeRows) Columns() []string { return []string{"value"} }
  83. func (r *timingFakeRows) Close() error {
  84. time.Sleep(fakeDriverDelay)
  85. return nil
  86. }
  87. func (r *timingFakeRows) Next(dest []driver.Value) error {
  88. time.Sleep(fakeDriverDelay)
  89. if r.index >= len(r.values) {
  90. return io.EOF
  91. }
  92. copy(dest, r.values[r.index])
  93. r.index++
  94. return nil
  95. }
  96. type timingFakeTx struct{}
  97. func (t *timingFakeTx) Commit() error {
  98. time.Sleep(fakeDriverDelay)
  99. return nil
  100. }
  101. func (t *timingFakeTx) Rollback() error {
  102. time.Sleep(fakeDriverDelay)
  103. return nil
  104. }
  105. func metricDuration(t *testing.T, header, metric string) float64 {
  106. t.Helper()
  107. re := regexp.MustCompile(`(?:^|, )` + regexp.QuoteMeta(metric) + `;dur=([0-9]+(?:\.[0-9]+)?)`)
  108. match := re.FindStringSubmatch(header)
  109. if len(match) != 2 {
  110. t.Fatalf("metric %q missing from header %q", metric, header)
  111. }
  112. value, err := strconv.ParseFloat(match[1], 64)
  113. if err != nil {
  114. t.Fatalf("parse %s duration: %v", metric, err)
  115. }
  116. return value
  117. }
  118. func TestServerTimingConnectorRecordsDriverCallsWithoutRowLifetime(t *testing.T) {
  119. startedAt := time.Now()
  120. collector := servertiming.New(startedAt)
  121. ctx := servertiming.WithCollector(context.Background(), collector)
  122. wrapped := newServerTimingConnector(timingFakeConnector{conn: newTimingFakeConn()})
  123. rawConn, err := wrapped.Connect(ctx)
  124. if err != nil {
  125. t.Fatal(err)
  126. }
  127. conn, ok := rawConn.(*serverTimingConn)
  128. if !ok {
  129. t.Fatalf("Connect() returned %T, want *serverTimingConn", rawConn)
  130. }
  131. if _, err := conn.ExecContext(ctx, "sensitive update", nil); err != nil {
  132. t.Fatal(err)
  133. }
  134. rows, err := conn.QueryContext(ctx, "sensitive select", nil)
  135. if err != nil {
  136. t.Fatal(err)
  137. }
  138. values := make([]driver.Value, 1)
  139. if err := rows.Next(values); err != nil {
  140. t.Fatal(err)
  141. }
  142. // Application work between row reads must remain app time.
  143. time.Sleep(30 * time.Millisecond)
  144. if err := rows.Next(values); err != io.EOF {
  145. t.Fatalf("rows.Next() = %v, want EOF", err)
  146. }
  147. if err := rows.Close(); err != nil {
  148. t.Fatal(err)
  149. }
  150. header := collector.HeaderValue(time.Now(), "bypass")
  151. if !strings.Contains(header, `queries=2`) {
  152. t.Fatalf("header %q does not report two SQL operations", header)
  153. }
  154. if strings.Contains(header, "sensitive") {
  155. t.Fatalf("SQL text leaked into header: %q", header)
  156. }
  157. if app, db := metricDuration(t, header, "app"), metricDuration(t, header, "db"); app <= db {
  158. t.Fatalf("row processing gap was counted as DB time: app=%.1fms db=%.1fms header=%q", app, db, header)
  159. }
  160. }
  161. func TestServerTimingPreparedStatementsAndTransactions(t *testing.T) {
  162. collector := servertiming.New(time.Now())
  163. ctx := servertiming.WithCollector(context.Background(), collector)
  164. conn := &serverTimingConn{Conn: newTimingFakeConn()}
  165. stmt, err := conn.PrepareContext(ctx, "prepare sensitive statement")
  166. if err != nil {
  167. t.Fatal(err)
  168. }
  169. timedStmt, ok := stmt.(*serverTimingStmt)
  170. if !ok {
  171. t.Fatalf("PrepareContext() returned %T, want *serverTimingStmt", stmt)
  172. }
  173. if _, err := timedStmt.ExecContext(ctx, nil); err != nil {
  174. t.Fatal(err)
  175. }
  176. rows, err := timedStmt.QueryContext(ctx, nil)
  177. if err != nil {
  178. t.Fatal(err)
  179. }
  180. if err := rows.Close(); err != nil {
  181. t.Fatal(err)
  182. }
  183. tx, err := conn.BeginTx(ctx, driver.TxOptions{})
  184. if err != nil {
  185. t.Fatal(err)
  186. }
  187. if err := tx.Commit(); err != nil {
  188. t.Fatal(err)
  189. }
  190. if err := conn.Ping(ctx); err != nil {
  191. t.Fatal(err)
  192. }
  193. if err := conn.ResetSession(ctx); err != nil {
  194. t.Fatal(err)
  195. }
  196. header := collector.HeaderValue(time.Now(), "bypass")
  197. if !strings.Contains(header, `queries=3`) {
  198. t.Fatalf("header %q does not report prepare, exec, and query operations", header)
  199. }
  200. if metricDuration(t, header, "db") <= 0 {
  201. t.Fatalf("DB duration was not recorded: %q", header)
  202. }
  203. }
  204. func TestNamedValuesRejectNamedParameters(t *testing.T) {
  205. if _, err := namedValues([]driver.NamedValue{{Name: "secret", Value: 1}}); err == nil {
  206. t.Fatal("namedValues accepted a named parameter")
  207. }
  208. values, err := namedValues([]driver.NamedValue{{Ordinal: 1, Value: "value"}})
  209. if err != nil {
  210. t.Fatal(err)
  211. }
  212. if len(values) != 1 || values[0] != "value" {
  213. t.Fatalf("namedValues() = %#v", values)
  214. }
  215. }