api_test.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package api // import "miniflux.app/v2/internal/api"
  4. import (
  5. "encoding/json"
  6. "net/http"
  7. "net/http/httptest"
  8. "runtime"
  9. "testing"
  10. "miniflux.app/v2/internal/version"
  11. )
  12. func TestNewHandlerHandlesOptionsRequests(t *testing.T) {
  13. handler := NewHandler(nil, nil)
  14. r := httptest.NewRequest(http.MethodOptions, "/v1/users", nil)
  15. w := httptest.NewRecorder()
  16. handler.ServeHTTP(w, r)
  17. if got := w.Code; got != http.StatusNoContent {
  18. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusNoContent)
  19. }
  20. if got := w.Header().Get("Access-Control-Allow-Origin"); got != "*" {
  21. t.Fatalf(`Unexpected Access-Control-Allow-Origin header, got %q`, got)
  22. }
  23. if got := w.Header().Get("Access-Control-Allow-Methods"); got != "GET, POST, PUT, DELETE, OPTIONS" {
  24. t.Fatalf(`Unexpected Access-Control-Allow-Methods header, got %q`, got)
  25. }
  26. if got := w.Header().Get("Access-Control-Allow-Headers"); got != "X-Auth-Token, Authorization, Content-Type, Accept" {
  27. t.Fatalf(`Unexpected Access-Control-Allow-Headers header, got %q`, got)
  28. }
  29. if got := w.Header().Get("Access-Control-Max-Age"); got != "3600" {
  30. t.Fatalf(`Unexpected Access-Control-Max-Age header, got %q`, got)
  31. }
  32. }
  33. func TestVersionHandler(t *testing.T) {
  34. h := &handler{}
  35. r := httptest.NewRequest(http.MethodGet, "/v1/version", nil)
  36. w := httptest.NewRecorder()
  37. h.versionHandler(w, r)
  38. if got := w.Code; got != http.StatusOK {
  39. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusOK)
  40. }
  41. if got := w.Header().Get("Content-Type"); got != "application/json" {
  42. t.Fatalf(`Unexpected Content-Type header, got %q`, got)
  43. }
  44. var responseBody versionResponse
  45. if err := json.NewDecoder(w.Body).Decode(&responseBody); err != nil {
  46. t.Fatalf("Unexpected JSON decoding error: %v", err)
  47. }
  48. if responseBody.Version != version.Version {
  49. t.Fatalf(`Unexpected version, got %q instead of %q`, responseBody.Version, version.Version)
  50. }
  51. if responseBody.Commit != version.Commit {
  52. t.Fatalf(`Unexpected commit, got %q instead of %q`, responseBody.Commit, version.Commit)
  53. }
  54. if responseBody.BuildDate != version.BuildDate {
  55. t.Fatalf(`Unexpected build date, got %q instead of %q`, responseBody.BuildDate, version.BuildDate)
  56. }
  57. if responseBody.GoVersion != runtime.Version() {
  58. t.Fatalf(`Unexpected Go version, got %q instead of %q`, responseBody.GoVersion, runtime.Version())
  59. }
  60. if responseBody.Compiler != runtime.Compiler {
  61. t.Fatalf(`Unexpected compiler, got %q instead of %q`, responseBody.Compiler, runtime.Compiler)
  62. }
  63. if responseBody.Arch != runtime.GOARCH {
  64. t.Fatalf(`Unexpected architecture, got %q instead of %q`, responseBody.Arch, runtime.GOARCH)
  65. }
  66. if responseBody.OS != runtime.GOOS {
  67. t.Fatalf(`Unexpected OS, got %q instead of %q`, responseBody.OS, runtime.GOOS)
  68. }
  69. }
  70. func TestGetEntryIDsHandlerRequiresAuthentication(t *testing.T) {
  71. handler := NewHandler(nil, nil)
  72. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids", nil)
  73. w := httptest.NewRecorder()
  74. handler.ServeHTTP(w, r)
  75. if got := w.Code; got != http.StatusUnauthorized {
  76. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusUnauthorized)
  77. }
  78. }
  79. func TestGetEntryIDsHandlerRejectsInvalidStarredParam(t *testing.T) {
  80. handler := NewHandler(nil, nil)
  81. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids?starred=maybe", nil)
  82. w := httptest.NewRecorder()
  83. handler.ServeHTTP(w, r)
  84. // Unauthenticated request should be rejected before param validation.
  85. if got := w.Code; got != http.StatusUnauthorized {
  86. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusUnauthorized)
  87. }
  88. }
  89. func TestGetEntryIDsHandlerRejectsInvalidStatusParam(t *testing.T) {
  90. handler := NewHandler(nil, nil)
  91. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids?status=invalid", nil)
  92. w := httptest.NewRecorder()
  93. handler.ServeHTTP(w, r)
  94. // Unauthenticated request should be rejected before param validation.
  95. if got := w.Code; got != http.StatusUnauthorized {
  96. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusUnauthorized)
  97. }
  98. }
  99. func TestParseEntryIDsParamsDefaults(t *testing.T) {
  100. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids", nil)
  101. limit, offset := parseEntryIDsParams(r)
  102. if limit != 10000 {
  103. t.Fatalf(`Expected default limit 10000, got %d`, limit)
  104. }
  105. if offset != 0 {
  106. t.Fatalf(`Expected default offset 0, got %d`, offset)
  107. }
  108. }
  109. func TestParseEntryIDsParamsCustomValues(t *testing.T) {
  110. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids?limit=500&offset=100", nil)
  111. limit, offset := parseEntryIDsParams(r)
  112. if limit != 500 {
  113. t.Fatalf(`Expected limit 500, got %d`, limit)
  114. }
  115. if offset != 100 {
  116. t.Fatalf(`Expected offset 100, got %d`, offset)
  117. }
  118. }
  119. func TestParseEntryIDsParamsLimitCappedAtMaximum(t *testing.T) {
  120. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids?limit=99999", nil)
  121. limit, _ := parseEntryIDsParams(r)
  122. if limit != 10000 {
  123. t.Fatalf(`Expected limit capped at 10000, got %d`, limit)
  124. }
  125. }
  126. func TestParseEntryIDsParamsZeroLimitUsesDefault(t *testing.T) {
  127. r := httptest.NewRequest(http.MethodGet, "/v1/entries/ids?limit=0", nil)
  128. limit, _ := parseEntryIDsParams(r)
  129. if limit != 10000 {
  130. t.Fatalf(`Expected zero limit to use default 10000, got %d`, limit)
  131. }
  132. }
  133. func TestNewHandlerSupportsBasePathStripping(t *testing.T) {
  134. scenarios := []struct {
  135. name string
  136. prefix string
  137. path string
  138. }{
  139. {name: "empty base path", prefix: "", path: "/v1/users"},
  140. {name: "non empty base path", prefix: "/base", path: "/base/v1/users"},
  141. }
  142. for _, scenario := range scenarios {
  143. t.Run(scenario.name, func(t *testing.T) {
  144. handler := http.StripPrefix(scenario.prefix, NewHandler(nil, nil))
  145. r := httptest.NewRequest(http.MethodOptions, scenario.path, nil)
  146. w := httptest.NewRecorder()
  147. handler.ServeHTTP(w, r)
  148. if got := w.Code; got != http.StatusNoContent {
  149. t.Fatalf(`Unexpected status code, got %d instead of %d`, got, http.StatusNoContent)
  150. }
  151. })
  152. }
  153. }