searchApiInput.test.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /* @vitest-environment node */
  2. import { describe, it, expect } from "vitest";
  3. import { ApiClientError } from "@/lib/frontend/apiClient";
  4. import { SEARCH_SCOPE } from "@/lib/frontend/search/urlState";
  5. import { buildSearchApiInput } from "./searchApiInput.js";
  6. describe("lib/frontend/search/searchApiInput", () => {
  7. it("returns {input:null} when q is missing", () => {
  8. const { input, error } = buildSearchApiInput({
  9. urlState: {
  10. q: null,
  11. scope: SEARCH_SCOPE.SINGLE,
  12. branch: "NL01",
  13. branches: [],
  14. from: null,
  15. to: null,
  16. },
  17. routeBranch: "NL01",
  18. user: { role: "admin", branchId: null },
  19. });
  20. expect(input).toBe(null);
  21. expect(error).toBe(null);
  22. });
  23. it("branch users always use routeBranch (single) and ignore scope", () => {
  24. const { input, error } = buildSearchApiInput({
  25. urlState: {
  26. q: "x",
  27. scope: SEARCH_SCOPE.ALL,
  28. branch: "NL99",
  29. branches: ["NL06"],
  30. from: null,
  31. to: null,
  32. },
  33. routeBranch: "NL01",
  34. user: { role: "branch", branchId: "NL01" },
  35. });
  36. expect(error).toBe(null);
  37. expect(input).toEqual({ q: "x", limit: 100, branch: "NL01" });
  38. });
  39. it("admin/dev: ALL uses scope=all", () => {
  40. const { input } = buildSearchApiInput({
  41. urlState: {
  42. q: "x",
  43. scope: SEARCH_SCOPE.ALL,
  44. branch: null,
  45. branches: [],
  46. from: null,
  47. to: null,
  48. },
  49. routeBranch: "NL01",
  50. user: { role: "admin", branchId: null },
  51. });
  52. expect(input).toEqual({ q: "x", limit: 100, scope: "all" });
  53. });
  54. it("admin/dev: MULTI uses scope=multi + branches", () => {
  55. const { input, error } = buildSearchApiInput({
  56. urlState: {
  57. q: "x",
  58. scope: SEARCH_SCOPE.MULTI,
  59. branch: null,
  60. branches: ["NL06", "NL20"],
  61. from: null,
  62. to: null,
  63. },
  64. routeBranch: "NL01",
  65. user: { role: "dev", branchId: null },
  66. cursor: "abc",
  67. limit: 100,
  68. });
  69. expect(error).toBe(null);
  70. expect(input).toEqual({
  71. q: "x",
  72. limit: 100,
  73. cursor: "abc",
  74. scope: "multi",
  75. branches: ["NL06", "NL20"],
  76. });
  77. });
  78. it("admin/dev: MULTI without branches is treated as not-ready (no error)", () => {
  79. const { input, error } = buildSearchApiInput({
  80. urlState: {
  81. q: "x",
  82. scope: SEARCH_SCOPE.MULTI,
  83. branch: null,
  84. branches: [],
  85. from: null,
  86. to: null,
  87. },
  88. routeBranch: "NL01",
  89. user: { role: "admin", branchId: null },
  90. });
  91. expect(input).toBe(null);
  92. expect(error).toBe(null);
  93. });
  94. it("admin/dev: SINGLE uses routeBranch as branch param", () => {
  95. const { input } = buildSearchApiInput({
  96. urlState: {
  97. q: "x",
  98. scope: SEARCH_SCOPE.SINGLE,
  99. branch: "NL99",
  100. branches: [],
  101. from: null,
  102. to: null,
  103. },
  104. routeBranch: "NL01",
  105. user: { role: "admin", branchId: null },
  106. });
  107. expect(input).toEqual({ q: "x", limit: 100, branch: "NL01" });
  108. });
  109. it("includes from/to when valid", () => {
  110. const { input, error } = buildSearchApiInput({
  111. urlState: {
  112. q: "x",
  113. scope: SEARCH_SCOPE.SINGLE,
  114. branch: "NL01",
  115. branches: [],
  116. from: "2025-12-01",
  117. to: "2025-12-31",
  118. },
  119. routeBranch: "NL01",
  120. user: { role: "admin", branchId: null },
  121. });
  122. expect(error).toBe(null);
  123. expect(input).toEqual({
  124. q: "x",
  125. limit: 100,
  126. branch: "NL01",
  127. from: "2025-12-01",
  128. to: "2025-12-31",
  129. });
  130. });
  131. it("accepts from===to as a valid single-day search", () => {
  132. const { input, error } = buildSearchApiInput({
  133. urlState: {
  134. q: "x",
  135. scope: SEARCH_SCOPE.SINGLE,
  136. branch: "NL01",
  137. branches: [],
  138. from: "2025-12-01",
  139. to: "2025-12-01",
  140. },
  141. routeBranch: "NL01",
  142. user: { role: "admin", branchId: null },
  143. });
  144. expect(error).toBe(null);
  145. expect(input).toEqual({
  146. q: "x",
  147. limit: 100,
  148. branch: "NL01",
  149. from: "2025-12-01",
  150. to: "2025-12-01",
  151. });
  152. });
  153. it("returns a local validation error when from > to (no request should be sent)", () => {
  154. const { input, error } = buildSearchApiInput({
  155. urlState: {
  156. q: "x",
  157. scope: SEARCH_SCOPE.SINGLE,
  158. branch: "NL01",
  159. branches: [],
  160. from: "2025-12-31",
  161. to: "2025-12-01",
  162. },
  163. routeBranch: "NL01",
  164. user: { role: "admin", branchId: null },
  165. });
  166. expect(input).toBe(null);
  167. expect(error).toBeInstanceOf(ApiClientError);
  168. expect(error.code).toBe("VALIDATION_SEARCH_RANGE");
  169. });
  170. it("returns a local validation error for invalid date format", () => {
  171. const { input, error } = buildSearchApiInput({
  172. urlState: {
  173. q: "x",
  174. scope: SEARCH_SCOPE.SINGLE,
  175. branch: "NL01",
  176. branches: [],
  177. from: "2025/12/01",
  178. to: null,
  179. },
  180. routeBranch: "NL01",
  181. user: { role: "admin", branchId: null },
  182. });
  183. expect(input).toBe(null);
  184. expect(error).toBeInstanceOf(ApiClientError);
  185. expect(error.code).toBe("VALIDATION_SEARCH_DATE");
  186. });
  187. });