page-api-handler.spec.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. import type { IncomingMessage } from 'node:http';
  2. import { beforeEach, describe, expect, it } from 'vitest';
  3. import { pageApiModule } from './page-api-handler';
  4. describe('pageApiModule', () => {
  5. const mockRequest = {} as IncomingMessage;
  6. beforeEach(() => {
  7. // No mocks needed - test actual behavior
  8. });
  9. describe('canHandle', () => {
  10. it.each`
  11. description | url | expected
  12. ${'pages list endpoint'} | ${'/_api/v3/pages/list?path=/home'} | ${true}
  13. ${'subordinated list endpoint'} | ${'/_api/v3/pages/subordinated-list?path=/docs'} | ${true}
  14. ${'check page existence endpoint'} | ${'/_api/v3/page/check-page-existence?path=/wiki'} | ${true}
  15. ${'get page paths with descendant count endpoint'} | ${'/_api/v3/page/get-page-paths-with-descendant-count?paths=[]'} | ${true}
  16. ${'pages list without query'} | ${'/_api/v3/pages/list'} | ${true}
  17. ${'subordinated list without query'} | ${'/_api/v3/pages/subordinated-list'} | ${true}
  18. ${'check page existence without query'} | ${'/_api/v3/page/check-page-existence'} | ${true}
  19. ${'get page paths without query'} | ${'/_api/v3/page/get-page-paths-with-descendant-count'} | ${true}
  20. ${'other pages endpoint'} | ${'/_api/v3/pages/create'} | ${false}
  21. ${'different API version'} | ${'/_api/v2/pages/list'} | ${false}
  22. ${'non-page API'} | ${'/_api/v3/search'} | ${false}
  23. ${'regular page path'} | ${'/page/path'} | ${false}
  24. ${'root path'} | ${'/'} | ${false}
  25. ${'empty URL'} | ${''} | ${false}
  26. ${'partial match but different endpoint'} | ${'/_api/v3/pages-other/list'} | ${false}
  27. `('should return $expected for $description: $url', ({ url, expected }) => {
  28. const result = pageApiModule.canHandle(url);
  29. expect(result).toBe(expected);
  30. });
  31. });
  32. describe('handle', () => {
  33. describe('pages/list endpoint', () => {
  34. it('should anonymize path parameter when present', () => {
  35. const originalUrl = '/_api/v3/pages/list?path=/sensitive/path&limit=10';
  36. // Verify canHandle returns true for this URL
  37. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  38. const result = pageApiModule.handle(mockRequest, originalUrl);
  39. expect(result).toEqual({
  40. 'http.target': '/_api/v3/pages/list?path=%5BANONYMIZED%5D&limit=10',
  41. });
  42. });
  43. it('should return null when no path parameter is present', () => {
  44. const url = '/_api/v3/pages/list?limit=10&sort=updated';
  45. // Verify canHandle returns true for this URL
  46. expect(pageApiModule.canHandle(url)).toBe(true);
  47. const result = pageApiModule.handle(mockRequest, url);
  48. expect(result).toBeNull();
  49. });
  50. });
  51. describe('pages/subordinated-list endpoint', () => {
  52. it('should anonymize path parameter', () => {
  53. const originalUrl =
  54. '/_api/v3/pages/subordinated-list?path=/user/documents&offset=0';
  55. // Verify canHandle returns true for this URL
  56. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  57. const result = pageApiModule.handle(mockRequest, originalUrl);
  58. expect(result).toEqual({
  59. 'http.target':
  60. '/_api/v3/pages/subordinated-list?path=%5BANONYMIZED%5D&offset=0',
  61. });
  62. });
  63. it('should handle encoded path parameters', () => {
  64. const originalUrl =
  65. '/_api/v3/pages/subordinated-list?path=%2Fuser%2Fdocs&includeEmpty=true';
  66. // Verify canHandle returns true for this URL
  67. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  68. const result = pageApiModule.handle(mockRequest, originalUrl);
  69. expect(result).toEqual({
  70. 'http.target':
  71. '/_api/v3/pages/subordinated-list?path=%5BANONYMIZED%5D&includeEmpty=true',
  72. });
  73. });
  74. });
  75. describe('page/check-page-existence endpoint', () => {
  76. it('should anonymize path parameter', () => {
  77. const originalUrl =
  78. '/_api/v3/page/check-page-existence?path=/project/wiki';
  79. // Verify canHandle returns true for this URL
  80. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  81. const result = pageApiModule.handle(mockRequest, originalUrl);
  82. expect(result).toEqual({
  83. 'http.target':
  84. '/_api/v3/page/check-page-existence?path=%5BANONYMIZED%5D',
  85. });
  86. });
  87. it('should handle multiple parameters including path', () => {
  88. const originalUrl =
  89. '/_api/v3/page/check-page-existence?path=/docs/api&includePrivate=false';
  90. // Verify canHandle returns true for this URL
  91. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  92. const result = pageApiModule.handle(mockRequest, originalUrl);
  93. expect(result).toEqual({
  94. 'http.target':
  95. '/_api/v3/page/check-page-existence?path=%5BANONYMIZED%5D&includePrivate=false',
  96. });
  97. });
  98. });
  99. describe('page/get-page-paths-with-descendant-count endpoint', () => {
  100. it('should anonymize paths parameter when present', () => {
  101. const originalUrl =
  102. '/_api/v3/page/get-page-paths-with-descendant-count?paths=["/docs","/wiki"]';
  103. // Verify canHandle returns true for this URL
  104. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  105. const result = pageApiModule.handle(mockRequest, originalUrl);
  106. expect(result).toEqual({
  107. 'http.target':
  108. '/_api/v3/page/get-page-paths-with-descendant-count?paths=%5B%22%5BANONYMIZED%5D%22%5D',
  109. });
  110. });
  111. it('should handle encoded paths parameter', () => {
  112. const originalUrl =
  113. '/_api/v3/page/get-page-paths-with-descendant-count?paths=%5B%22%2Fdocs%22%5D';
  114. // Verify canHandle returns true for this URL
  115. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  116. const result = pageApiModule.handle(mockRequest, originalUrl);
  117. expect(result).toEqual({
  118. 'http.target':
  119. '/_api/v3/page/get-page-paths-with-descendant-count?paths=%5B%22%5BANONYMIZED%5D%22%5D',
  120. });
  121. });
  122. it('should return null when no paths parameter is present', () => {
  123. const url =
  124. '/_api/v3/page/get-page-paths-with-descendant-count?includeEmpty=true';
  125. // Verify canHandle returns true for this URL
  126. expect(pageApiModule.canHandle(url)).toBe(true);
  127. const result = pageApiModule.handle(mockRequest, url);
  128. expect(result).toBeNull();
  129. });
  130. });
  131. describe('mixed endpoint scenarios', () => {
  132. it('should handle pages/list endpoint without path parameter', () => {
  133. const url = '/_api/v3/pages/list?limit=20&sort=name';
  134. // Verify canHandle returns true for this URL
  135. expect(pageApiModule.canHandle(url)).toBe(true);
  136. const result = pageApiModule.handle(mockRequest, url);
  137. expect(result).toBeNull();
  138. });
  139. it('should handle subordinated-list endpoint without path parameter', () => {
  140. const url = '/_api/v3/pages/subordinated-list?includeEmpty=false';
  141. // Verify canHandle returns true for this URL
  142. expect(pageApiModule.canHandle(url)).toBe(true);
  143. const result = pageApiModule.handle(mockRequest, url);
  144. expect(result).toBeNull();
  145. });
  146. it('should handle check-page-existence endpoint without path parameter', () => {
  147. const url = '/_api/v3/page/check-page-existence?includePrivate=true';
  148. // Verify canHandle returns true for this URL
  149. expect(pageApiModule.canHandle(url)).toBe(true);
  150. const result = pageApiModule.handle(mockRequest, url);
  151. expect(result).toBeNull();
  152. });
  153. });
  154. describe('edge cases', () => {
  155. it('should handle empty path parameter', () => {
  156. const originalUrl = '/_api/v3/pages/list?path=&limit=5';
  157. // Verify canHandle returns true for this URL
  158. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  159. const result = pageApiModule.handle(mockRequest, originalUrl);
  160. expect(result).toEqual({
  161. 'http.target': '/_api/v3/pages/list?path=%5BANONYMIZED%5D&limit=5',
  162. });
  163. });
  164. it('should handle root path parameter', () => {
  165. const originalUrl = '/_api/v3/page/check-page-existence?path=/';
  166. // Verify canHandle returns true for this URL
  167. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  168. const result = pageApiModule.handle(mockRequest, originalUrl);
  169. expect(result).toEqual({
  170. 'http.target':
  171. '/_api/v3/page/check-page-existence?path=%5BANONYMIZED%5D',
  172. });
  173. });
  174. it('should handle empty paths array parameter', () => {
  175. const originalUrl =
  176. '/_api/v3/page/get-page-paths-with-descendant-count?paths=[]';
  177. // Verify canHandle returns true for this URL
  178. expect(pageApiModule.canHandle(originalUrl)).toBe(true);
  179. const result = pageApiModule.handle(mockRequest, originalUrl);
  180. expect(result).toEqual({
  181. 'http.target':
  182. '/_api/v3/page/get-page-paths-with-descendant-count?paths=%5BANONYMIZED%5D',
  183. });
  184. });
  185. });
  186. });
  187. });