wallabag_test.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package wallabag
  4. import (
  5. "encoding/json"
  6. "io"
  7. "net/http"
  8. "net/http/httptest"
  9. "strings"
  10. "testing"
  11. "miniflux.app/v2/internal/config"
  12. )
  13. func TestCreateEntry(t *testing.T) {
  14. configureIntegrationAllowPrivateNetworksOption(t)
  15. entryURL := "https://example.com"
  16. entryTitle := "title"
  17. entryContent := "content"
  18. tags := "tag1,tag2,tag3"
  19. tests := []struct {
  20. name string
  21. username string
  22. password string
  23. clientID string
  24. clientSecret string
  25. tags string
  26. onlyURL bool
  27. entryURL string
  28. entryTitle string
  29. entryContent string
  30. serverResponse func(w http.ResponseWriter, r *http.Request)
  31. wantErr bool
  32. errContains string
  33. }{
  34. {
  35. name: "successful entry creation with url only",
  36. wantErr: false,
  37. onlyURL: true,
  38. username: "username",
  39. password: "password",
  40. clientID: "clientId",
  41. clientSecret: "clientSecret",
  42. tags: tags,
  43. entryURL: entryURL,
  44. entryTitle: entryTitle,
  45. entryContent: entryContent,
  46. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  47. if strings.Contains(r.URL.Path, "/oauth/v2/token") {
  48. // Return success response
  49. w.WriteHeader(http.StatusOK)
  50. json.NewEncoder(w).Encode(map[string]any{
  51. "access_token": "test-token",
  52. "expires_in": 3600,
  53. "refresh_token": "token",
  54. "scope": "scope",
  55. "token_type": "token_type",
  56. })
  57. return
  58. }
  59. // Verify authorization header
  60. auth := r.Header.Get("Authorization")
  61. if auth != "Bearer test-token" {
  62. t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
  63. }
  64. // Verify content type
  65. contentType := r.Header.Get("Content-Type")
  66. if contentType != "application/json" {
  67. t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
  68. }
  69. // Parse and verify request
  70. body, _ := io.ReadAll(r.Body)
  71. var req map[string]any
  72. if err := json.Unmarshal(body, &req); err != nil {
  73. t.Errorf("Failed to parse request body: %v", err)
  74. }
  75. if requstEntryURL := req["url"]; requstEntryURL != entryURL {
  76. t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
  77. }
  78. if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
  79. t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
  80. }
  81. if _, ok := req["content"]; ok {
  82. t.Errorf("Expected entryContent to be empty, got value")
  83. }
  84. if requestTags := req["tags"]; requestTags != tags {
  85. t.Errorf("Expected tags %s, got %s", tags, requestTags)
  86. } // Return success response
  87. w.WriteHeader(http.StatusOK)
  88. },
  89. errContains: "",
  90. },
  91. {
  92. name: "successful entry creation with content",
  93. wantErr: false,
  94. onlyURL: false,
  95. username: "username",
  96. password: "password",
  97. clientID: "clientId",
  98. clientSecret: "clientSecret",
  99. tags: tags,
  100. entryURL: entryURL,
  101. entryTitle: entryTitle,
  102. entryContent: entryContent,
  103. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  104. if strings.Contains(r.URL.Path, "/oauth/v2/token") {
  105. // Return success response
  106. w.WriteHeader(http.StatusOK)
  107. json.NewEncoder(w).Encode(map[string]any{
  108. "access_token": "test-token",
  109. "expires_in": 3600,
  110. "refresh_token": "token",
  111. "scope": "scope",
  112. "token_type": "token_type",
  113. })
  114. return
  115. }
  116. // Verify authorization header
  117. auth := r.Header.Get("Authorization")
  118. if auth != "Bearer test-token" {
  119. t.Errorf("Expected Authorization header 'Bearer test-token', got %s", auth)
  120. }
  121. // Verify content type
  122. contentType := r.Header.Get("Content-Type")
  123. if contentType != "application/json" {
  124. t.Errorf("Expected Content-Type 'application/json', got %s", contentType)
  125. }
  126. // Parse and verify request
  127. body, _ := io.ReadAll(r.Body)
  128. var req map[string]any
  129. if err := json.Unmarshal(body, &req); err != nil {
  130. t.Errorf("Failed to parse request body: %v", err)
  131. }
  132. if requstEntryURL := req["url"]; requstEntryURL != entryURL {
  133. t.Errorf("Expected entryURL %s, got %s", entryURL, requstEntryURL)
  134. }
  135. if requestEntryTitle := req["title"]; requestEntryTitle != entryTitle {
  136. t.Errorf("Expected entryTitle %s, got %s", entryTitle, requestEntryTitle)
  137. }
  138. if requestEntryContent := req["content"]; requestEntryContent != entryContent {
  139. t.Errorf("Expected entryContent %s, got %s", entryContent, requestEntryContent)
  140. }
  141. if requestTags := req["tags"]; requestTags != tags {
  142. t.Errorf("Expected tags %s, got %s", tags, requestTags)
  143. } // Return success response
  144. w.WriteHeader(http.StatusOK)
  145. },
  146. errContains: "",
  147. },
  148. {
  149. name: "failed when unable to decode accessToken response",
  150. wantErr: true,
  151. onlyURL: true,
  152. username: "username",
  153. password: "password",
  154. clientID: "clientId",
  155. clientSecret: "clientSecret",
  156. tags: tags,
  157. entryURL: entryURL,
  158. entryTitle: entryTitle,
  159. entryContent: entryContent,
  160. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  161. if strings.Contains(r.URL.Path, "/oauth/v2/token") {
  162. // Return success response
  163. w.WriteHeader(http.StatusOK)
  164. w.Write([]byte("invalid json"))
  165. return
  166. }
  167. t.Error("Server should not be called when failed to get accessToken")
  168. },
  169. errContains: "unable to decode token response",
  170. },
  171. {
  172. name: "failed when saving entry",
  173. wantErr: true,
  174. onlyURL: true,
  175. username: "username",
  176. password: "password",
  177. clientID: "clientId",
  178. clientSecret: "clientSecret",
  179. tags: tags,
  180. entryURL: entryURL,
  181. entryTitle: entryTitle,
  182. entryContent: entryContent,
  183. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  184. if strings.Contains(r.URL.Path, "/oauth/v2/token") {
  185. // Return success response
  186. w.WriteHeader(http.StatusOK)
  187. json.NewEncoder(w).Encode(map[string]any{
  188. "access_token": "test-token",
  189. "expires_in": 3600,
  190. "refresh_token": "token",
  191. "scope": "scope",
  192. "token_type": "token_type",
  193. })
  194. return
  195. }
  196. w.WriteHeader(http.StatusUnauthorized)
  197. },
  198. errContains: "unable to get save entry",
  199. },
  200. {
  201. name: "failure due to no accessToken",
  202. wantErr: true,
  203. onlyURL: false,
  204. username: "username",
  205. password: "password",
  206. clientID: "clientId",
  207. clientSecret: "clientSecret",
  208. tags: tags,
  209. entryURL: entryURL,
  210. entryTitle: entryTitle,
  211. entryContent: entryContent,
  212. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  213. if strings.Contains(r.URL.Path, "/oauth/v2/token") {
  214. // Return error response
  215. w.WriteHeader(http.StatusUnauthorized)
  216. return
  217. }
  218. t.Error("Server should not be called when failed to get accessToken")
  219. },
  220. errContains: "unable to get access token",
  221. },
  222. {
  223. name: "failure due to missing client parameters",
  224. wantErr: true,
  225. onlyURL: false,
  226. tags: tags,
  227. entryURL: entryURL,
  228. entryTitle: entryTitle,
  229. entryContent: entryContent,
  230. serverResponse: func(w http.ResponseWriter, r *http.Request) {
  231. t.Error("Server should not be called when failed to get accessToken")
  232. },
  233. errContains: "wallabag: missing base URL, client ID, client secret, username or password",
  234. },
  235. }
  236. for _, tt := range tests {
  237. t.Run(tt.name, func(t *testing.T) {
  238. // Create test server if we have a server response function
  239. var serverURL string
  240. if tt.serverResponse != nil {
  241. server := httptest.NewServer(http.HandlerFunc(tt.serverResponse))
  242. defer server.Close()
  243. serverURL = server.URL
  244. }
  245. // Create client with test server URL
  246. client := NewClient(serverURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)
  247. // Call CreateBookmark
  248. err := client.CreateEntry(tt.entryURL, tt.entryTitle, tt.entryContent)
  249. // Check error expectations
  250. if tt.wantErr {
  251. if err == nil {
  252. t.Errorf("Expected error but got none")
  253. } else if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
  254. t.Errorf("Expected error containing '%s', got '%s'", tt.errContains, err.Error())
  255. }
  256. } else {
  257. if err != nil {
  258. t.Errorf("Unexpected error: %v", err)
  259. }
  260. }
  261. })
  262. }
  263. }
  264. func TestNewClient(t *testing.T) {
  265. tests := []struct {
  266. name string
  267. baseURL string
  268. clientID string
  269. clientSecret string
  270. username string
  271. password string
  272. tags string
  273. onlyURL bool
  274. }{
  275. {
  276. name: "with all parameters",
  277. baseURL: "https://wallabag.example.com",
  278. clientID: "clientID",
  279. clientSecret: "clientSecret",
  280. username: "wallabag",
  281. password: "wallabag",
  282. tags: "",
  283. onlyURL: true,
  284. },
  285. }
  286. for _, tt := range tests {
  287. t.Run(tt.name, func(t *testing.T) {
  288. client := NewClient(tt.baseURL, tt.clientID, tt.clientSecret, tt.username, tt.password, tt.tags, tt.onlyURL)
  289. if client.baseURL != tt.baseURL {
  290. t.Errorf("Expected.baseURL %s, got %s", tt.baseURL, client.baseURL)
  291. }
  292. if client.username != tt.username {
  293. t.Errorf("Expected username %s, got %s", tt.username, client.username)
  294. }
  295. if client.password != tt.password {
  296. t.Errorf("Expected password %s, got %s", tt.password, client.password)
  297. }
  298. if client.clientID != tt.clientID {
  299. t.Errorf("Expected clientID %s, got %s", tt.clientID, client.clientID)
  300. }
  301. if client.clientSecret != tt.clientSecret {
  302. t.Errorf("Expected clientSecret %s, got %s", tt.clientSecret, client.clientSecret)
  303. }
  304. if client.tags != tt.tags {
  305. t.Errorf("Expected tags %s, got %s", tt.tags, client.tags)
  306. }
  307. if client.onlyURL != tt.onlyURL {
  308. t.Errorf("Expected onlyURL %v, got %v", tt.onlyURL, client.onlyURL)
  309. }
  310. })
  311. }
  312. }
  313. func configureIntegrationAllowPrivateNetworksOption(t *testing.T) {
  314. t.Helper()
  315. t.Setenv("INTEGRATION_ALLOW_PRIVATE_NETWORKS", "1")
  316. configParser := config.NewConfigParser()
  317. parsedOptions, err := configParser.ParseEnvironmentVariables()
  318. if err != nil {
  319. t.Fatalf("Unable to configure test options: %v", err)
  320. }
  321. previousOptions := config.Opts
  322. config.Opts = parsedOptions
  323. t.Cleanup(func() {
  324. config.Opts = previousOptions
  325. })
  326. }