2
0

server_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. package httpserver
  2. import (
  3. "bytes"
  4. "compress/gzip"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "strings"
  9. "testing"
  10. "time"
  11. "github.com/goccy/go-json"
  12. "github.com/danfragoso/pizzasql-next/pkg/executor"
  13. "github.com/danfragoso/pizzasql-next/pkg/storage"
  14. )
  15. func setupTestServer(t *testing.T) (*Server, *storage.KVPool) {
  16. pool, err := storage.NewKVPool("localhost:8085", 5, 5*time.Second)
  17. if err != nil {
  18. t.Skip("PizzaKV not available, skipping HTTP server tests")
  19. }
  20. schema := storage.NewSchemaManager(pool, "test_http_db")
  21. table := storage.NewTableManager(pool, schema, "test_http_db")
  22. exec := executor.New(schema, table)
  23. config := DefaultConfig()
  24. config.EnableAuth = false
  25. server := New(config, exec, schema)
  26. return server, pool
  27. }
  28. func TestQueryEndpoint(t *testing.T) {
  29. server, pool := setupTestServer(t)
  30. defer pool.Close()
  31. // Create test table
  32. req := QueryRequest{
  33. SQL: "CREATE TABLE test_users (id INTEGER PRIMARY KEY, name TEXT)",
  34. }
  35. body, _ := json.Marshal(req)
  36. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  37. w := httptest.NewRecorder()
  38. server.handleQuery(w, r)
  39. if w.Code != http.StatusOK {
  40. t.Errorf("expected status 200, got %d", w.Code)
  41. }
  42. // Insert data
  43. req = QueryRequest{
  44. SQL: "INSERT INTO test_users (id, name) VALUES (1, 'Alice')",
  45. }
  46. body, _ = json.Marshal(req)
  47. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  48. w = httptest.NewRecorder()
  49. server.handleQuery(w, r)
  50. if w.Code != http.StatusOK {
  51. t.Errorf("expected status 200, got %d", w.Code)
  52. }
  53. var resp QueryResponse
  54. json.NewDecoder(w.Body).Decode(&resp)
  55. if resp.RowsAffected != 1 {
  56. t.Errorf("expected 1 row affected, got %d", resp.RowsAffected)
  57. }
  58. // Query data
  59. req = QueryRequest{
  60. SQL: "SELECT * FROM test_users WHERE id = 1",
  61. }
  62. body, _ = json.Marshal(req)
  63. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  64. w = httptest.NewRecorder()
  65. server.handleQuery(w, r)
  66. if w.Code != http.StatusOK {
  67. t.Errorf("expected status 200, got %d", w.Code)
  68. }
  69. json.NewDecoder(w.Body).Decode(&resp)
  70. if len(resp.Rows) != 1 {
  71. t.Errorf("expected 1 row, got %d", len(resp.Rows))
  72. }
  73. // Cleanup
  74. req = QueryRequest{SQL: "DROP TABLE test_users"}
  75. body, _ = json.Marshal(req)
  76. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  77. w = httptest.NewRecorder()
  78. server.handleQuery(w, r)
  79. }
  80. func TestExecuteEndpoint(t *testing.T) {
  81. server, pool := setupTestServer(t)
  82. defer pool.Close()
  83. // Create table first
  84. createReq := QueryRequest{
  85. SQL: "CREATE TABLE test_batch (id INTEGER PRIMARY KEY, value TEXT)",
  86. }
  87. body, _ := json.Marshal(createReq)
  88. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  89. w := httptest.NewRecorder()
  90. server.handleQuery(w, r)
  91. // Batch insert
  92. req := ExecuteRequest{
  93. Statements: []QueryRequest{
  94. {SQL: "INSERT INTO test_batch (id, value) VALUES (1, 'first')"},
  95. {SQL: "INSERT INTO test_batch (id, value) VALUES (2, 'second')"},
  96. },
  97. Transaction: true,
  98. }
  99. body, _ = json.Marshal(req)
  100. r = httptest.NewRequest(http.MethodPost, "/execute", bytes.NewReader(body))
  101. w = httptest.NewRecorder()
  102. server.handleExecute(w, r)
  103. if w.Code != http.StatusOK {
  104. t.Errorf("expected status 200, got %d", w.Code)
  105. }
  106. var resp ExecuteResponse
  107. json.NewDecoder(w.Body).Decode(&resp)
  108. if len(resp.Results) != 2 {
  109. t.Errorf("expected 2 results, got %d", len(resp.Results))
  110. }
  111. // Cleanup
  112. dropReq := QueryRequest{SQL: "DROP TABLE test_batch"}
  113. body, _ = json.Marshal(dropReq)
  114. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  115. w = httptest.NewRecorder()
  116. server.handleQuery(w, r)
  117. }
  118. func TestSchemaEndpoints(t *testing.T) {
  119. server, pool := setupTestServer(t)
  120. defer pool.Close()
  121. // Create test table
  122. createReq := QueryRequest{
  123. SQL: "CREATE TABLE test_schema (id INTEGER PRIMARY KEY, name TEXT)",
  124. }
  125. body, _ := json.Marshal(createReq)
  126. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  127. w := httptest.NewRecorder()
  128. server.handleQuery(w, r)
  129. // List tables
  130. r = httptest.NewRequest(http.MethodGet, "/schema/tables", nil)
  131. w = httptest.NewRecorder()
  132. server.handleSchemaTables(w, r)
  133. if w.Code != http.StatusOK {
  134. t.Errorf("expected status 200, got %d", w.Code)
  135. }
  136. var tablesResp map[string]interface{}
  137. json.NewDecoder(w.Body).Decode(&tablesResp)
  138. tables := tablesResp["tables"].([]interface{})
  139. found := false
  140. for _, table := range tables {
  141. if table.(string) == "test_schema" {
  142. found = true
  143. break
  144. }
  145. }
  146. if !found {
  147. t.Error("test_schema table not found in list")
  148. }
  149. // Get table schema
  150. r = httptest.NewRequest(http.MethodGet, "/schema/tables/test_schema", nil)
  151. w = httptest.NewRecorder()
  152. server.handleSchemaTable(w, r)
  153. if w.Code != http.StatusOK {
  154. t.Errorf("expected status 200, got %d", w.Code)
  155. }
  156. // Cleanup
  157. dropReq := QueryRequest{SQL: "DROP TABLE test_schema"}
  158. body, _ = json.Marshal(dropReq)
  159. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  160. w = httptest.NewRecorder()
  161. server.handleQuery(w, r)
  162. }
  163. func TestHealthEndpoint(t *testing.T) {
  164. server, pool := setupTestServer(t)
  165. defer pool.Close()
  166. r := httptest.NewRequest(http.MethodGet, "/health", nil)
  167. w := httptest.NewRecorder()
  168. server.handleHealth(w, r)
  169. if w.Code != http.StatusOK {
  170. t.Errorf("expected status 200, got %d", w.Code)
  171. }
  172. var resp map[string]interface{}
  173. json.NewDecoder(w.Body).Decode(&resp)
  174. if resp["status"] != "ok" {
  175. t.Errorf("expected status 'ok', got '%v'", resp["status"])
  176. }
  177. }
  178. func TestStatsEndpoint(t *testing.T) {
  179. server, pool := setupTestServer(t)
  180. defer pool.Close()
  181. r := httptest.NewRequest(http.MethodGet, "/stats", nil)
  182. w := httptest.NewRecorder()
  183. server.handleStats(w, r)
  184. if w.Code != http.StatusOK {
  185. t.Errorf("expected status 200, got %d", w.Code)
  186. }
  187. var resp map[string]interface{}
  188. json.NewDecoder(w.Body).Decode(&resp)
  189. if _, ok := resp["queriesExecuted"]; !ok {
  190. t.Error("expected queriesExecuted in response")
  191. }
  192. }
  193. func TestReadOnlyMode(t *testing.T) {
  194. server, pool := setupTestServer(t)
  195. defer pool.Close()
  196. // Try to insert in readonly mode
  197. req := QueryRequest{
  198. SQL: "INSERT INTO test (id) VALUES (1)",
  199. }
  200. body, _ := json.Marshal(req)
  201. r := httptest.NewRequest(http.MethodPost, "/query?readonly=true", bytes.NewReader(body))
  202. w := httptest.NewRecorder()
  203. server.handleQuery(w, r)
  204. if w.Code != http.StatusForbidden {
  205. t.Errorf("expected status 403, got %d", w.Code)
  206. }
  207. }
  208. func TestCORSMiddleware(t *testing.T) {
  209. server, pool := setupTestServer(t)
  210. defer pool.Close()
  211. r := httptest.NewRequest(http.MethodOptions, "/query", nil)
  212. w := httptest.NewRecorder()
  213. handler := server.corsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
  214. handler.ServeHTTP(w, r)
  215. if w.Header().Get("Access-Control-Allow-Origin") != "*" {
  216. t.Error("CORS headers not set correctly")
  217. }
  218. }
  219. func TestParameterizedQuery(t *testing.T) {
  220. server, pool := setupTestServer(t)
  221. defer pool.Close()
  222. // Create test table
  223. createReq := QueryRequest{
  224. SQL: "CREATE TABLE test_params (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)",
  225. }
  226. body, _ := json.Marshal(createReq)
  227. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  228. w := httptest.NewRecorder()
  229. server.handleQuery(w, r)
  230. // Insert with parameters
  231. req := QueryRequest{
  232. SQL: "INSERT INTO test_params (id, name, age) VALUES (?, ?, ?)",
  233. Params: []interface{}{1, "Alice", 30},
  234. }
  235. body, _ = json.Marshal(req)
  236. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  237. w = httptest.NewRecorder()
  238. server.handleQuery(w, r)
  239. if w.Code != http.StatusOK {
  240. t.Errorf("expected status 200, got %d: %s", w.Code, w.Body.String())
  241. }
  242. // Query with parameters
  243. req = QueryRequest{
  244. SQL: "SELECT * FROM test_params WHERE name = ?",
  245. Params: []interface{}{"Alice"},
  246. }
  247. body, _ = json.Marshal(req)
  248. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  249. w = httptest.NewRecorder()
  250. server.handleQuery(w, r)
  251. if w.Code != http.StatusOK {
  252. t.Errorf("expected status 200, got %d", w.Code)
  253. }
  254. var resp QueryResponse
  255. json.NewDecoder(w.Body).Decode(&resp)
  256. if len(resp.Rows) != 1 {
  257. t.Errorf("expected 1 row, got %d", len(resp.Rows))
  258. }
  259. // Cleanup
  260. dropReq := QueryRequest{SQL: "DROP TABLE test_params"}
  261. body, _ = json.Marshal(dropReq)
  262. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  263. w = httptest.NewRecorder()
  264. server.handleQuery(w, r)
  265. }
  266. func TestSubstituteParams(t *testing.T) {
  267. tests := []struct {
  268. sql string
  269. params []interface{}
  270. expected string
  271. }{
  272. {"SELECT * FROM users WHERE id = ?", []interface{}{42}, "SELECT * FROM users WHERE id = 42"},
  273. {"SELECT * FROM users WHERE name = ?", []interface{}{"Alice"}, "SELECT * FROM users WHERE name = 'Alice'"},
  274. {"SELECT * FROM users WHERE name = ?", []interface{}{"O'Brien"}, "SELECT * FROM users WHERE name = 'O''Brien'"},
  275. {"INSERT INTO t (a, b) VALUES (?, ?)", []interface{}{1, "test"}, "INSERT INTO t (a, b) VALUES (1, 'test')"},
  276. {"SELECT * FROM t WHERE x = ?", []interface{}{nil}, "SELECT * FROM t WHERE x = NULL"},
  277. {"SELECT * FROM t WHERE x = ?", []interface{}{true}, "SELECT * FROM t WHERE x = 1"},
  278. {"SELECT * FROM t WHERE x = ?", []interface{}{false}, "SELECT * FROM t WHERE x = 0"},
  279. {"SELECT * FROM t WHERE x = ?", []interface{}{3.14}, "SELECT * FROM t WHERE x = 3.14"},
  280. }
  281. for _, tt := range tests {
  282. result := substituteParams(tt.sql, tt.params)
  283. if result != tt.expected {
  284. t.Errorf("substituteParams(%q, %v) = %q, want %q", tt.sql, tt.params, result, tt.expected)
  285. }
  286. }
  287. }
  288. func TestCompressionMiddleware(t *testing.T) {
  289. server, pool := setupTestServer(t)
  290. defer pool.Close()
  291. // Create a handler that returns some JSON
  292. testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  293. w.Header().Set("Content-Type", "application/json")
  294. w.Write([]byte(`{"message": "hello world"}`))
  295. })
  296. // Wrap with compression middleware
  297. handler := server.compressionMiddleware(testHandler)
  298. // Request with gzip accept header
  299. r := httptest.NewRequest(http.MethodGet, "/test", nil)
  300. r.Header.Set("Accept-Encoding", "gzip")
  301. w := httptest.NewRecorder()
  302. handler.ServeHTTP(w, r)
  303. // Check that response is gzip encoded
  304. if w.Header().Get("Content-Encoding") != "gzip" {
  305. t.Error("expected gzip Content-Encoding header")
  306. }
  307. // Decompress and verify content
  308. gr, err := gzip.NewReader(w.Body)
  309. if err != nil {
  310. t.Fatalf("failed to create gzip reader: %v", err)
  311. }
  312. defer gr.Close()
  313. body, err := io.ReadAll(gr)
  314. if err != nil {
  315. t.Fatalf("failed to read gzip body: %v", err)
  316. }
  317. if string(body) != `{"message": "hello world"}` {
  318. t.Errorf("unexpected body: %s", string(body))
  319. }
  320. }
  321. func TestCompressionMiddlewareNoGzip(t *testing.T) {
  322. server, pool := setupTestServer(t)
  323. defer pool.Close()
  324. // Create a handler that returns some text
  325. testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  326. w.Write([]byte("hello world"))
  327. })
  328. // Wrap with compression middleware
  329. handler := server.compressionMiddleware(testHandler)
  330. // Request WITHOUT gzip accept header
  331. r := httptest.NewRequest(http.MethodGet, "/test", nil)
  332. w := httptest.NewRecorder()
  333. handler.ServeHTTP(w, r)
  334. // Check that response is NOT gzip encoded
  335. if w.Header().Get("Content-Encoding") == "gzip" {
  336. t.Error("should not have gzip encoding without Accept-Encoding header")
  337. }
  338. if w.Body.String() != "hello world" {
  339. t.Errorf("unexpected body: %s", w.Body.String())
  340. }
  341. }
  342. func TestMetricsEndpoint(t *testing.T) {
  343. server, pool := setupTestServer(t)
  344. defer pool.Close()
  345. r := httptest.NewRequest(http.MethodGet, "/metrics", nil)
  346. w := httptest.NewRecorder()
  347. server.handleMetrics(w, r)
  348. if w.Code != http.StatusOK {
  349. t.Errorf("expected status 200, got %d", w.Code)
  350. }
  351. body := w.Body.String()
  352. // Check for expected Prometheus metrics
  353. expectedMetrics := []string{
  354. "pizzasql_queries_total",
  355. "pizzasql_queries_executed_total",
  356. "pizzasql_tables_count",
  357. "pizzasql_uptime_seconds",
  358. "pizzasql_info",
  359. }
  360. for _, metric := range expectedMetrics {
  361. if !strings.Contains(body, metric) {
  362. t.Errorf("expected metric %s in response", metric)
  363. }
  364. }
  365. // Check content type
  366. contentType := w.Header().Get("Content-Type")
  367. if !strings.Contains(contentType, "text/plain") {
  368. t.Errorf("expected text/plain content type, got %s", contentType)
  369. }
  370. }
  371. func TestInferType(t *testing.T) {
  372. tests := []struct {
  373. value interface{}
  374. expected string
  375. }{
  376. {nil, "NULL"},
  377. {int64(42), "INTEGER"},
  378. {int(42), "INTEGER"},
  379. {3.14, "REAL"},
  380. {"hello", "TEXT"},
  381. {[]byte{1, 2, 3}, "BLOB"},
  382. {true, "INTEGER"},
  383. {false, "INTEGER"},
  384. }
  385. for _, tt := range tests {
  386. result := inferType(tt.value)
  387. if result != tt.expected {
  388. t.Errorf("inferType(%v) = %s, want %s", tt.value, result, tt.expected)
  389. }
  390. }
  391. }
  392. func TestTransactionEndpoints(t *testing.T) {
  393. server, pool := setupTestServer(t)
  394. defer pool.Close()
  395. // Test BEGIN transaction
  396. r := httptest.NewRequest(http.MethodPost, "/transaction/begin", nil)
  397. w := httptest.NewRecorder()
  398. server.handleTransactionBegin(w, r)
  399. if w.Code != http.StatusOK {
  400. t.Errorf("BEGIN: expected status 200, got %d", w.Code)
  401. }
  402. var beginResp map[string]interface{}
  403. json.NewDecoder(w.Body).Decode(&beginResp)
  404. if beginResp["status"] != "started" {
  405. t.Errorf("BEGIN: expected status 'started', got '%v'", beginResp["status"])
  406. }
  407. // Test COMMIT transaction
  408. r = httptest.NewRequest(http.MethodPost, "/transaction/commit", nil)
  409. w = httptest.NewRecorder()
  410. server.handleTransactionCommit(w, r)
  411. if w.Code != http.StatusOK {
  412. t.Errorf("COMMIT: expected status 200, got %d", w.Code)
  413. }
  414. var commitResp map[string]interface{}
  415. json.NewDecoder(w.Body).Decode(&commitResp)
  416. if commitResp["status"] != "committed" {
  417. t.Errorf("COMMIT: expected status 'committed', got '%v'", commitResp["status"])
  418. }
  419. // Test BEGIN again for rollback test
  420. r = httptest.NewRequest(http.MethodPost, "/transaction/begin", nil)
  421. w = httptest.NewRecorder()
  422. server.handleTransactionBegin(w, r)
  423. // Test ROLLBACK transaction
  424. r = httptest.NewRequest(http.MethodPost, "/transaction/rollback", nil)
  425. w = httptest.NewRecorder()
  426. server.handleTransactionRollback(w, r)
  427. if w.Code != http.StatusOK {
  428. t.Errorf("ROLLBACK: expected status 200, got %d", w.Code)
  429. }
  430. var rollbackResp map[string]interface{}
  431. json.NewDecoder(w.Body).Decode(&rollbackResp)
  432. if rollbackResp["status"] != "rolled back" {
  433. t.Errorf("ROLLBACK: expected status 'rolled back', got '%v'", rollbackResp["status"])
  434. }
  435. }
  436. func TestTransactionEndpointsMethodNotAllowed(t *testing.T) {
  437. server, pool := setupTestServer(t)
  438. defer pool.Close()
  439. // Test GET on transaction endpoints (should fail)
  440. endpoints := []struct {
  441. path string
  442. handler func(http.ResponseWriter, *http.Request)
  443. }{
  444. {"/transaction/begin", server.handleTransactionBegin},
  445. {"/transaction/commit", server.handleTransactionCommit},
  446. {"/transaction/rollback", server.handleTransactionRollback},
  447. }
  448. for _, ep := range endpoints {
  449. r := httptest.NewRequest(http.MethodGet, ep.path, nil)
  450. w := httptest.NewRecorder()
  451. ep.handler(w, r)
  452. if w.Code != http.StatusMethodNotAllowed {
  453. t.Errorf("%s: expected status 405 for GET, got %d", ep.path, w.Code)
  454. }
  455. }
  456. }
  457. func TestAuthMiddleware(t *testing.T) {
  458. pool, err := storage.NewKVPool("localhost:8085", 5, 5*time.Second)
  459. if err != nil {
  460. t.Skip("PizzaKV not available, skipping auth tests")
  461. }
  462. defer pool.Close()
  463. schema := storage.NewSchemaManager(pool, "test_auth_db")
  464. table := storage.NewTableManager(pool, schema, "test_auth_db")
  465. exec := executor.New(schema, table)
  466. config := DefaultConfig()
  467. config.EnableAuth = true
  468. config.APIKeys = []string{"test-api-key-123", "another-key-456"}
  469. server := New(config, exec, schema)
  470. testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  471. w.WriteHeader(http.StatusOK)
  472. w.Write([]byte("OK"))
  473. })
  474. handler := server.authMiddleware(testHandler)
  475. // Test without Authorization header
  476. r := httptest.NewRequest(http.MethodGet, "/test", nil)
  477. w := httptest.NewRecorder()
  478. handler.ServeHTTP(w, r)
  479. if w.Code != http.StatusUnauthorized {
  480. t.Errorf("expected 401 without auth header, got %d", w.Code)
  481. }
  482. // Test with invalid API key
  483. r = httptest.NewRequest(http.MethodGet, "/test", nil)
  484. r.Header.Set("Authorization", "Bearer invalid-key")
  485. w = httptest.NewRecorder()
  486. handler.ServeHTTP(w, r)
  487. if w.Code != http.StatusForbidden {
  488. t.Errorf("expected 403 with invalid key, got %d", w.Code)
  489. }
  490. // Test with valid API key
  491. r = httptest.NewRequest(http.MethodGet, "/test", nil)
  492. r.Header.Set("Authorization", "Bearer test-api-key-123")
  493. w = httptest.NewRecorder()
  494. handler.ServeHTTP(w, r)
  495. if w.Code != http.StatusOK {
  496. t.Errorf("expected 200 with valid key, got %d", w.Code)
  497. }
  498. }
  499. func TestQueryEndpointErrors(t *testing.T) {
  500. server, pool := setupTestServer(t)
  501. defer pool.Close()
  502. // Test with missing SQL
  503. req := QueryRequest{
  504. SQL: "",
  505. }
  506. body, _ := json.Marshal(req)
  507. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  508. w := httptest.NewRecorder()
  509. server.handleQuery(w, r)
  510. if w.Code != http.StatusBadRequest {
  511. t.Errorf("expected 400 for empty SQL, got %d", w.Code)
  512. }
  513. // Test with invalid JSON
  514. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader([]byte("invalid json")))
  515. w = httptest.NewRecorder()
  516. server.handleQuery(w, r)
  517. if w.Code != http.StatusBadRequest {
  518. t.Errorf("expected 400 for invalid JSON, got %d", w.Code)
  519. }
  520. // Test with syntax error
  521. req = QueryRequest{
  522. SQL: "SELEC * FORM users",
  523. }
  524. body, _ = json.Marshal(req)
  525. r = httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  526. w = httptest.NewRecorder()
  527. server.handleQuery(w, r)
  528. if w.Code != http.StatusBadRequest {
  529. t.Errorf("expected 400 for SQL syntax error, got %d", w.Code)
  530. }
  531. }
  532. func TestPrettyPrintOption(t *testing.T) {
  533. server, pool := setupTestServer(t)
  534. defer pool.Close()
  535. req := QueryRequest{
  536. SQL: "SELECT 1 as num",
  537. }
  538. body, _ := json.Marshal(req)
  539. // Without pretty
  540. r := httptest.NewRequest(http.MethodPost, "/query", bytes.NewReader(body))
  541. w := httptest.NewRecorder()
  542. server.handleQuery(w, r)
  543. normalResponse := w.Body.String()
  544. // With pretty
  545. body, _ = json.Marshal(req)
  546. r = httptest.NewRequest(http.MethodPost, "/query?pretty=true", bytes.NewReader(body))
  547. w = httptest.NewRecorder()
  548. server.handleQuery(w, r)
  549. prettyResponse := w.Body.String()
  550. // Pretty response should be longer due to formatting
  551. if len(prettyResponse) <= len(normalResponse) {
  552. t.Error("pretty response should be longer than normal response")
  553. }
  554. // Pretty response should contain newlines
  555. if !strings.Contains(prettyResponse, "\n") {
  556. t.Error("pretty response should contain newlines")
  557. }
  558. }
  559. func TestSchemaTableNotFound(t *testing.T) {
  560. server, pool := setupTestServer(t)
  561. defer pool.Close()
  562. r := httptest.NewRequest(http.MethodGet, "/schema/tables/nonexistent_table_xyz", nil)
  563. w := httptest.NewRecorder()
  564. server.handleSchemaTable(w, r)
  565. if w.Code != http.StatusNotFound {
  566. t.Errorf("expected 404 for nonexistent table, got %d", w.Code)
  567. }
  568. }