mcp-wordpress 1.1.7 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/README.md +388 -66
  2. package/dist/cache/CacheInvalidation.d.ts +118 -0
  3. package/dist/cache/CacheInvalidation.d.ts.map +1 -0
  4. package/dist/cache/CacheInvalidation.js +349 -0
  5. package/dist/cache/CacheInvalidation.js.map +1 -0
  6. package/dist/cache/CacheManager.d.ts +143 -0
  7. package/dist/cache/CacheManager.d.ts.map +1 -0
  8. package/dist/cache/CacheManager.js +308 -0
  9. package/dist/cache/CacheManager.js.map +1 -0
  10. package/dist/cache/HttpCacheWrapper.d.ts +121 -0
  11. package/dist/cache/HttpCacheWrapper.d.ts.map +1 -0
  12. package/dist/cache/HttpCacheWrapper.js +280 -0
  13. package/dist/cache/HttpCacheWrapper.js.map +1 -0
  14. package/dist/cache/__tests__/CacheInvalidation.test.d.ts +5 -0
  15. package/dist/cache/__tests__/CacheInvalidation.test.d.ts.map +1 -0
  16. package/dist/cache/__tests__/CacheInvalidation.test.js +236 -0
  17. package/dist/cache/__tests__/CacheInvalidation.test.js.map +1 -0
  18. package/dist/cache/__tests__/CacheManager.test.d.ts +5 -0
  19. package/dist/cache/__tests__/CacheManager.test.d.ts.map +1 -0
  20. package/dist/cache/__tests__/CacheManager.test.js +233 -0
  21. package/dist/cache/__tests__/CacheManager.test.js.map +1 -0
  22. package/dist/cache/__tests__/CachedWordPressClient.test.d.ts +5 -0
  23. package/dist/cache/__tests__/CachedWordPressClient.test.d.ts.map +1 -0
  24. package/dist/cache/__tests__/CachedWordPressClient.test.js +228 -0
  25. package/dist/cache/__tests__/CachedWordPressClient.test.js.map +1 -0
  26. package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts +5 -0
  27. package/dist/cache/__tests__/HttpCacheWrapper.test.d.ts.map +1 -0
  28. package/dist/cache/__tests__/HttpCacheWrapper.test.js +296 -0
  29. package/dist/cache/__tests__/HttpCacheWrapper.test.js.map +1 -0
  30. package/dist/cache/index.d.ts +12 -0
  31. package/dist/cache/index.d.ts.map +1 -0
  32. package/dist/cache/index.js +9 -0
  33. package/dist/cache/index.js.map +1 -0
  34. package/dist/client/CachedWordPressClient.d.ts +160 -0
  35. package/dist/client/CachedWordPressClient.d.ts.map +1 -0
  36. package/dist/client/CachedWordPressClient.js +338 -0
  37. package/dist/client/CachedWordPressClient.js.map +1 -0
  38. package/dist/client/WordPressClient.d.ts +81 -0
  39. package/dist/client/WordPressClient.d.ts.map +1 -0
  40. package/dist/client/WordPressClient.js +354 -0
  41. package/dist/client/WordPressClient.js.map +1 -0
  42. package/dist/config/ConfigurationSchema.d.ts +281 -0
  43. package/dist/config/ConfigurationSchema.d.ts.map +1 -0
  44. package/dist/config/ConfigurationSchema.js +205 -0
  45. package/dist/config/ConfigurationSchema.js.map +1 -0
  46. package/dist/config/ServerConfiguration.d.ts +38 -0
  47. package/dist/config/ServerConfiguration.d.ts.map +1 -0
  48. package/dist/config/ServerConfiguration.js +158 -0
  49. package/dist/config/ServerConfiguration.js.map +1 -0
  50. package/dist/docs/DocumentationGenerator.d.ts +184 -0
  51. package/dist/docs/DocumentationGenerator.d.ts.map +1 -0
  52. package/dist/docs/DocumentationGenerator.js +735 -0
  53. package/dist/docs/DocumentationGenerator.js.map +1 -0
  54. package/dist/docs/MarkdownFormatter.d.ts +84 -0
  55. package/dist/docs/MarkdownFormatter.d.ts.map +1 -0
  56. package/dist/docs/MarkdownFormatter.js +448 -0
  57. package/dist/docs/MarkdownFormatter.js.map +1 -0
  58. package/dist/docs/index.d.ts +8 -0
  59. package/dist/docs/index.d.ts.map +1 -0
  60. package/dist/docs/index.js +7 -0
  61. package/dist/docs/index.js.map +1 -0
  62. package/dist/index.d.ts +1 -4
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +12 -212
  65. package/dist/index.js.map +1 -1
  66. package/dist/performance/AnomalyDetector.d.ts +63 -0
  67. package/dist/performance/AnomalyDetector.d.ts.map +1 -0
  68. package/dist/performance/AnomalyDetector.js +222 -0
  69. package/dist/performance/AnomalyDetector.js.map +1 -0
  70. package/dist/performance/BenchmarkAnalyzer.d.ts +67 -0
  71. package/dist/performance/BenchmarkAnalyzer.d.ts.map +1 -0
  72. package/dist/performance/BenchmarkAnalyzer.js +301 -0
  73. package/dist/performance/BenchmarkAnalyzer.js.map +1 -0
  74. package/dist/performance/MetricsCollector.d.ts +139 -0
  75. package/dist/performance/MetricsCollector.d.ts.map +1 -0
  76. package/dist/performance/MetricsCollector.js +320 -0
  77. package/dist/performance/MetricsCollector.js.map +1 -0
  78. package/dist/performance/PerformanceAnalytics.d.ts +162 -0
  79. package/dist/performance/PerformanceAnalytics.d.ts.map +1 -0
  80. package/dist/performance/PerformanceAnalytics.js +554 -0
  81. package/dist/performance/PerformanceAnalytics.js.map +1 -0
  82. package/dist/performance/PerformanceMonitor.d.ts +202 -0
  83. package/dist/performance/PerformanceMonitor.d.ts.map +1 -0
  84. package/dist/performance/PerformanceMonitor.js +478 -0
  85. package/dist/performance/PerformanceMonitor.js.map +1 -0
  86. package/dist/performance/TrendAnalyzer.d.ts +69 -0
  87. package/dist/performance/TrendAnalyzer.d.ts.map +1 -0
  88. package/dist/performance/TrendAnalyzer.js +203 -0
  89. package/dist/performance/TrendAnalyzer.js.map +1 -0
  90. package/dist/performance/index.d.ts +11 -0
  91. package/dist/performance/index.d.ts.map +1 -0
  92. package/dist/performance/index.js +8 -0
  93. package/dist/performance/index.js.map +1 -0
  94. package/dist/security/InputValidator.d.ts +215 -0
  95. package/dist/security/InputValidator.d.ts.map +1 -0
  96. package/dist/security/InputValidator.js +278 -0
  97. package/dist/security/InputValidator.js.map +1 -0
  98. package/dist/security/SecurityConfig.d.ts +129 -0
  99. package/dist/security/SecurityConfig.d.ts.map +1 -0
  100. package/dist/security/SecurityConfig.js +262 -0
  101. package/dist/security/SecurityConfig.js.map +1 -0
  102. package/dist/server/ConnectionTester.d.ts +24 -0
  103. package/dist/server/ConnectionTester.d.ts.map +1 -0
  104. package/dist/server/ConnectionTester.js +61 -0
  105. package/dist/server/ConnectionTester.js.map +1 -0
  106. package/dist/server/ToolRegistry.d.ts +46 -0
  107. package/dist/server/ToolRegistry.d.ts.map +1 -0
  108. package/dist/server/ToolRegistry.js +148 -0
  109. package/dist/server/ToolRegistry.js.map +1 -0
  110. package/dist/tools/BaseToolClass.d.ts +76 -0
  111. package/dist/tools/BaseToolClass.d.ts.map +1 -0
  112. package/dist/tools/BaseToolClass.js +104 -0
  113. package/dist/tools/BaseToolClass.js.map +1 -0
  114. package/dist/tools/BaseToolManager.d.ts +26 -0
  115. package/dist/tools/BaseToolManager.d.ts.map +1 -0
  116. package/dist/tools/BaseToolManager.js +56 -0
  117. package/dist/tools/BaseToolManager.js.map +1 -0
  118. package/dist/tools/base.d.ts +37 -0
  119. package/dist/tools/base.d.ts.map +1 -0
  120. package/dist/tools/base.js +60 -0
  121. package/dist/tools/base.js.map +1 -0
  122. package/dist/tools/cache.d.ts +260 -0
  123. package/dist/tools/cache.d.ts.map +1 -0
  124. package/dist/tools/cache.js +237 -0
  125. package/dist/tools/cache.js.map +1 -0
  126. package/dist/tools/index.d.ts +2 -0
  127. package/dist/tools/index.d.ts.map +1 -1
  128. package/dist/tools/index.js +2 -0
  129. package/dist/tools/index.js.map +1 -1
  130. package/dist/tools/performance.d.ts +63 -0
  131. package/dist/tools/performance.d.ts.map +1 -0
  132. package/dist/tools/performance.js +865 -0
  133. package/dist/tools/performance.js.map +1 -0
  134. package/dist/types/client.d.ts +1 -0
  135. package/dist/types/client.d.ts.map +1 -1
  136. package/dist/types/client.js.map +1 -1
  137. package/dist/utils/toolWrapper.d.ts +4 -0
  138. package/dist/utils/toolWrapper.d.ts.map +1 -1
  139. package/dist/utils/toolWrapper.js +11 -0
  140. package/dist/utils/toolWrapper.js.map +1 -1
  141. package/dist/utils/validation.d.ts +68 -0
  142. package/dist/utils/validation.d.ts.map +1 -0
  143. package/dist/utils/validation.js +185 -0
  144. package/dist/utils/validation.js.map +1 -0
  145. package/docs/CACHING.md +340 -0
  146. package/docs/DOCKER.md +451 -0
  147. package/docs/PERFORMANCE_MONITORING.md +471 -0
  148. package/docs/SECURITY_TESTING.md +393 -0
  149. package/docs/api/README.md +200 -0
  150. package/docs/api/categories/auth.md +40 -0
  151. package/docs/api/categories/cache.md +41 -0
  152. package/docs/api/categories/comment.md +44 -0
  153. package/docs/api/categories/media.md +43 -0
  154. package/docs/api/categories/page.md +43 -0
  155. package/docs/api/categories/performance.md +44 -0
  156. package/docs/api/categories/post.md +43 -0
  157. package/docs/api/categories/site.md +43 -0
  158. package/docs/api/categories/taxonomy.md +47 -0
  159. package/docs/api/categories/user.md +43 -0
  160. package/docs/api/openapi.json +3305 -0
  161. package/docs/api/summary.json +12 -0
  162. package/docs/api/tools/wp_approve_comment.md +98 -0
  163. package/docs/api/tools/wp_cache_clear.md +120 -0
  164. package/docs/api/tools/wp_cache_info.md +119 -0
  165. package/docs/api/tools/wp_cache_stats.md +119 -0
  166. package/docs/api/tools/wp_cache_warm.md +119 -0
  167. package/docs/api/tools/wp_create_application_password.md +102 -0
  168. package/docs/api/tools/wp_create_category.md +102 -0
  169. package/docs/api/tools/wp_create_comment.md +128 -0
  170. package/docs/api/tools/wp_create_page.md +135 -0
  171. package/docs/api/tools/wp_create_post.md +147 -0
  172. package/docs/api/tools/wp_create_tag.md +101 -0
  173. package/docs/api/tools/wp_create_user.md +135 -0
  174. package/docs/api/tools/wp_delete_application_password.md +101 -0
  175. package/docs/api/tools/wp_delete_category.md +100 -0
  176. package/docs/api/tools/wp_delete_comment.md +101 -0
  177. package/docs/api/tools/wp_delete_media.md +108 -0
  178. package/docs/api/tools/wp_delete_page.md +108 -0
  179. package/docs/api/tools/wp_delete_post.md +117 -0
  180. package/docs/api/tools/wp_delete_tag.md +100 -0
  181. package/docs/api/tools/wp_delete_user.md +108 -0
  182. package/docs/api/tools/wp_get_application_passwords.md +103 -0
  183. package/docs/api/tools/wp_get_auth_status.md +101 -0
  184. package/docs/api/tools/wp_get_category.md +103 -0
  185. package/docs/api/tools/wp_get_comment.md +103 -0
  186. package/docs/api/tools/wp_get_current_user.md +101 -0
  187. package/docs/api/tools/wp_get_media.md +103 -0
  188. package/docs/api/tools/wp_get_page.md +103 -0
  189. package/docs/api/tools/wp_get_page_revisions.md +103 -0
  190. package/docs/api/tools/wp_get_post.md +112 -0
  191. package/docs/api/tools/wp_get_post_revisions.md +103 -0
  192. package/docs/api/tools/wp_get_site_settings.md +108 -0
  193. package/docs/api/tools/wp_get_tag.md +103 -0
  194. package/docs/api/tools/wp_get_user.md +103 -0
  195. package/docs/api/tools/wp_list_categories.md +111 -0
  196. package/docs/api/tools/wp_list_comments.md +111 -0
  197. package/docs/api/tools/wp_list_media.md +145 -0
  198. package/docs/api/tools/wp_list_pages.md +145 -0
  199. package/docs/api/tools/wp_list_posts.md +156 -0
  200. package/docs/api/tools/wp_list_tags.md +110 -0
  201. package/docs/api/tools/wp_list_users.md +111 -0
  202. package/docs/api/tools/wp_performance_alerts.md +162 -0
  203. package/docs/api/tools/wp_performance_benchmark.md +160 -0
  204. package/docs/api/tools/wp_performance_export.md +162 -0
  205. package/docs/api/tools/wp_performance_history.md +161 -0
  206. package/docs/api/tools/wp_performance_optimize.md +162 -0
  207. package/docs/api/tools/wp_performance_stats.md +160 -0
  208. package/docs/api/tools/wp_search_site.md +99 -0
  209. package/docs/api/tools/wp_spam_comment.md +98 -0
  210. package/docs/api/tools/wp_switch_auth_method.md +122 -0
  211. package/docs/api/tools/wp_test_auth.md +96 -0
  212. package/docs/api/tools/wp_update_category.md +102 -0
  213. package/docs/api/tools/wp_update_comment.md +127 -0
  214. package/docs/api/tools/wp_update_media.md +129 -0
  215. package/docs/api/tools/wp_update_page.md +135 -0
  216. package/docs/api/tools/wp_update_post.md +144 -0
  217. package/docs/api/tools/wp_update_site_settings.md +127 -0
  218. package/docs/api/tools/wp_update_tag.md +102 -0
  219. package/docs/api/tools/wp_update_user.md +134 -0
  220. package/docs/api/tools/wp_upload_media.md +131 -0
  221. package/docs/api/types/WordPressPost.md +39 -0
  222. package/docs/contract-testing.md +183 -0
  223. package/docs/developer/NPM_AUTH_SETUP.md +3 -3
  224. package/docs/wordpress-rest-api-authentication-troubleshooting.md +218 -0
  225. package/package.json +84 -64
  226. package/src/cache/CacheInvalidation.ts +421 -0
  227. package/src/cache/CacheManager.ts +391 -0
  228. package/src/cache/HttpCacheWrapper.ts +372 -0
  229. package/src/cache/__tests__/CacheInvalidation.test.ts +299 -0
  230. package/src/cache/__tests__/CacheManager.test.ts +300 -0
  231. package/src/cache/__tests__/CachedWordPressClient.test.ts +304 -0
  232. package/src/cache/__tests__/HttpCacheWrapper.test.ts +359 -0
  233. package/src/cache/index.ts +26 -0
  234. package/src/client/CachedWordPressClient.ts +442 -0
  235. package/src/config/ConfigurationSchema.ts +246 -0
  236. package/src/config/ServerConfiguration.ts +215 -0
  237. package/src/docs/DocumentationGenerator.ts +952 -0
  238. package/src/docs/MarkdownFormatter.ts +494 -0
  239. package/src/docs/index.ts +21 -0
  240. package/src/index.ts +14 -274
  241. package/src/performance/MetricsCollector.ts +447 -0
  242. package/src/performance/PerformanceAnalytics.ts +762 -0
  243. package/src/performance/PerformanceMonitor.ts +649 -0
  244. package/src/performance/index.ts +28 -0
  245. package/src/security/InputValidator.ts +319 -0
  246. package/src/security/SecurityConfig.ts +301 -0
  247. package/src/server/ConnectionTester.ts +74 -0
  248. package/src/server/ToolRegistry.ts +194 -0
  249. package/src/tools/BaseToolManager.ts +66 -0
  250. package/src/tools/cache.ts +259 -0
  251. package/src/tools/index.ts +2 -0
  252. package/src/tools/performance.ts +948 -0
  253. package/src/types/client.ts +1 -0
  254. package/src/utils/toolWrapper.ts +11 -0
  255. package/src/utils/validation.ts +259 -0
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Tests for CacheManager
3
+ */
4
+
5
+ import { CacheManager, CachePresets } from '../CacheManager.js';
6
+
7
+ describe('CacheManager', () => {
8
+ let cacheManager: CacheManager;
9
+
10
+ beforeEach(() => {
11
+ cacheManager = new CacheManager({
12
+ maxSize: 100,
13
+ defaultTTL: 1000,
14
+ enableLRU: true,
15
+ enableStats: true
16
+ });
17
+ });
18
+
19
+ afterEach(() => {
20
+ cacheManager.clear();
21
+ });
22
+
23
+ describe('Basic Operations', () => {
24
+ test('should store and retrieve values', () => {
25
+ const key = 'test-key';
26
+ const value = { data: 'test value' };
27
+
28
+ cacheManager.set(key, value);
29
+ const retrieved = cacheManager.get(key);
30
+
31
+ expect(retrieved).toEqual(value);
32
+ });
33
+
34
+ test('should return null for non-existent keys', () => {
35
+ const result = cacheManager.get('non-existent');
36
+ expect(result).toBeNull();
37
+ });
38
+
39
+ test('should check if key exists', () => {
40
+ const key = 'exists-test';
41
+ expect(cacheManager.has(key)).toBe(false);
42
+
43
+ cacheManager.set(key, 'value');
44
+ expect(cacheManager.has(key)).toBe(true);
45
+ });
46
+
47
+ test('should delete entries', () => {
48
+ const key = 'delete-test';
49
+ cacheManager.set(key, 'value');
50
+
51
+ expect(cacheManager.has(key)).toBe(true);
52
+ const deleted = cacheManager.delete(key);
53
+
54
+ expect(deleted).toBe(true);
55
+ expect(cacheManager.has(key)).toBe(false);
56
+ });
57
+
58
+ test('should clear all entries', () => {
59
+ cacheManager.set('key1', 'value1');
60
+ cacheManager.set('key2', 'value2');
61
+
62
+ expect(cacheManager.getStats().totalSize).toBe(2);
63
+
64
+ cacheManager.clear();
65
+
66
+ expect(cacheManager.getStats().totalSize).toBe(0);
67
+ expect(cacheManager.has('key1')).toBe(false);
68
+ expect(cacheManager.has('key2')).toBe(false);
69
+ });
70
+ });
71
+
72
+ describe('TTL and Expiration', () => {
73
+ test('should expire entries after TTL', async () => {
74
+ const key = 'ttl-test';
75
+ const shortTTL = 50; // 50ms
76
+
77
+ cacheManager.set(key, 'value', shortTTL);
78
+ expect(cacheManager.get(key)).toBe('value');
79
+
80
+ // Wait for expiration
81
+ await new Promise(resolve => setTimeout(resolve, 100));
82
+
83
+ expect(cacheManager.get(key)).toBeNull();
84
+ });
85
+
86
+ test('should not expire entries before TTL', () => {
87
+ const key = 'no-expire-test';
88
+ const longTTL = 10000; // 10 seconds
89
+
90
+ cacheManager.set(key, 'value', longTTL);
91
+ expect(cacheManager.get(key)).toBe('value');
92
+ });
93
+
94
+ test('should use default TTL when not specified', () => {
95
+ const key = 'default-ttl-test';
96
+ cacheManager.set(key, 'value');
97
+
98
+ const entry = cacheManager.getEntry(key);
99
+ expect(entry?.ttl).toBe(1000);
100
+ });
101
+ });
102
+
103
+ describe('Key Generation', () => {
104
+ test('should generate consistent keys for same parameters', () => {
105
+ const siteId = 'site1';
106
+ const endpoint = 'posts';
107
+ const params = { per_page: 10, status: 'publish' };
108
+
109
+ const key1 = cacheManager.generateKey(siteId, endpoint, params);
110
+ const key2 = cacheManager.generateKey(siteId, endpoint, params);
111
+
112
+ expect(key1).toBe(key2);
113
+ });
114
+
115
+ test('should generate different keys for different parameters', () => {
116
+ const siteId = 'site1';
117
+ const endpoint = 'posts';
118
+ const params1 = { per_page: 10 };
119
+ const params2 = { per_page: 20 };
120
+
121
+ const key1 = cacheManager.generateKey(siteId, endpoint, params1);
122
+ const key2 = cacheManager.generateKey(siteId, endpoint, params2);
123
+
124
+ expect(key1).not.toBe(key2);
125
+ });
126
+
127
+ test('should include site ID in key', () => {
128
+ const endpoint = 'posts';
129
+ const params = { per_page: 10 };
130
+
131
+ const key1 = cacheManager.generateKey('site1', endpoint, params);
132
+ const key2 = cacheManager.generateKey('site2', endpoint, params);
133
+
134
+ expect(key1).toContain('site1:');
135
+ expect(key2).toContain('site2:');
136
+ expect(key1).not.toBe(key2);
137
+ });
138
+
139
+ test('should handle undefined parameters', () => {
140
+ const key1 = cacheManager.generateKey('site1', 'posts');
141
+ const key2 = cacheManager.generateKey('site1', 'posts', undefined);
142
+ const key3 = cacheManager.generateKey('site1', 'posts', {});
143
+
144
+ expect(key1).toBe(key2);
145
+ expect(key1).toBe(key3);
146
+ });
147
+ });
148
+
149
+ describe('Site-Specific Operations', () => {
150
+ test('should clear cache for specific site', () => {
151
+ cacheManager.set('site1:posts:abc123', 'value1');
152
+ cacheManager.set('site1:pages:def456', 'value2');
153
+ cacheManager.set('site2:posts:ghi789', 'value3');
154
+
155
+ const cleared = cacheManager.clearSite('site1');
156
+
157
+ expect(cleared).toBe(2);
158
+ expect(cacheManager.has('site1:posts:abc123')).toBe(false);
159
+ expect(cacheManager.has('site1:pages:def456')).toBe(false);
160
+ expect(cacheManager.has('site2:posts:ghi789')).toBe(true);
161
+ });
162
+
163
+ test('should clear cache matching pattern', () => {
164
+ cacheManager.set('site1:posts:list', 'value1');
165
+ cacheManager.set('site1:posts:123', 'value2');
166
+ cacheManager.set('site1:pages:456', 'value3');
167
+
168
+ const pattern = /site1:posts/;
169
+ const cleared = cacheManager.clearPattern(pattern);
170
+
171
+ expect(cleared).toBe(2);
172
+ expect(cacheManager.has('site1:posts:list')).toBe(false);
173
+ expect(cacheManager.has('site1:posts:123')).toBe(false);
174
+ expect(cacheManager.has('site1:pages:456')).toBe(true);
175
+ });
176
+ });
177
+
178
+ describe('LRU Eviction', () => {
179
+ test('should evict least recently used items when at capacity', () => {
180
+ const smallCache = new CacheManager({
181
+ maxSize: 3,
182
+ defaultTTL: 10000,
183
+ enableLRU: true,
184
+ enableStats: true
185
+ });
186
+
187
+ // Fill cache to capacity
188
+ smallCache.set('key1', 'value1');
189
+ smallCache.set('key2', 'value2');
190
+ smallCache.set('key3', 'value3');
191
+
192
+ expect(smallCache.getStats().totalSize).toBe(3);
193
+
194
+ // Access key1 to make it more recently used
195
+ smallCache.get('key1');
196
+
197
+ // Add new item, should evict key2 (least recently used)
198
+ smallCache.set('key4', 'value4');
199
+
200
+ expect(smallCache.has('key1')).toBe(true); // Recently accessed
201
+ expect(smallCache.has('key2')).toBe(false); // Should be evicted
202
+ expect(smallCache.has('key3')).toBe(true); // Recently added
203
+ expect(smallCache.has('key4')).toBe(true); // Just added
204
+ expect(smallCache.getStats().evictions).toBe(1);
205
+ });
206
+ });
207
+
208
+ describe('ETags and Conditional Requests', () => {
209
+ test('should store and retrieve ETags', () => {
210
+ const key = 'etag-test';
211
+ const value = { data: 'test' };
212
+ const etag = '"abc123"';
213
+
214
+ cacheManager.set(key, value, 1000, etag);
215
+
216
+ const entry = cacheManager.getEntry(key);
217
+ expect(entry?.etag).toBe(etag);
218
+ });
219
+
220
+ test('should support conditional request headers', () => {
221
+ const key = 'conditional-test';
222
+ const etag = '"abc123"';
223
+ const lastModified = 'Wed, 21 Oct 2015 07:28:00 GMT';
224
+
225
+ cacheManager.set(key, { data: 'test' }, 1000, etag, lastModified);
226
+
227
+ expect(cacheManager.supportsConditionalRequest(key)).toBe(true);
228
+
229
+ const headers = cacheManager.getConditionalHeaders(key);
230
+ expect(headers['If-None-Match']).toBe(etag);
231
+ expect(headers['If-Modified-Since']).toBe(lastModified);
232
+ });
233
+
234
+ test('should not support conditional requests without ETags', () => {
235
+ const key = 'no-etag-test';
236
+ cacheManager.set(key, { data: 'test' });
237
+
238
+ expect(cacheManager.supportsConditionalRequest(key)).toBe(false);
239
+ expect(cacheManager.getConditionalHeaders(key)).toEqual({});
240
+ });
241
+ });
242
+
243
+ describe('Statistics', () => {
244
+ test('should track cache hits and misses', () => {
245
+ const key = 'stats-test';
246
+
247
+ // Miss
248
+ cacheManager.get(key);
249
+ let stats = cacheManager.getStats();
250
+ expect(stats.misses).toBe(1);
251
+ expect(stats.hits).toBe(0);
252
+
253
+ // Hit
254
+ cacheManager.set(key, 'value');
255
+ cacheManager.get(key);
256
+ stats = cacheManager.getStats();
257
+ expect(stats.hits).toBe(1);
258
+ expect(stats.misses).toBe(1);
259
+ expect(stats.hitRate).toBe(0.5);
260
+ });
261
+
262
+ test('should track total size', () => {
263
+ const stats1 = cacheManager.getStats();
264
+ expect(stats1.totalSize).toBe(0);
265
+
266
+ cacheManager.set('key1', 'value1');
267
+ cacheManager.set('key2', 'value2');
268
+
269
+ const stats2 = cacheManager.getStats();
270
+ expect(stats2.totalSize).toBe(2);
271
+ });
272
+
273
+ test('should calculate hit rate correctly', () => {
274
+ cacheManager.set('key1', 'value1');
275
+
276
+ // 3 hits, 2 misses = 60% hit rate
277
+ cacheManager.get('key1'); // hit
278
+ cacheManager.get('key1'); // hit
279
+ cacheManager.get('key1'); // hit
280
+ cacheManager.get('key2'); // miss
281
+ cacheManager.get('key3'); // miss
282
+
283
+ const stats = cacheManager.getStats();
284
+ expect(stats.hits).toBe(3);
285
+ expect(stats.misses).toBe(2);
286
+ expect(stats.hitRate).toBe(0.6);
287
+ });
288
+ });
289
+
290
+ describe('Cache Presets', () => {
291
+ test('should have predefined cache presets', () => {
292
+ expect(CachePresets.STATIC.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);
293
+ expect(CachePresets.SEMI_STATIC.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);
294
+ expect(CachePresets.SESSION.ttl).toBeGreaterThan(CachePresets.DYNAMIC.ttl);
295
+
296
+ expect(CachePresets.STATIC.cacheControl).toContain('public');
297
+ expect(CachePresets.SESSION.cacheControl).toContain('private');
298
+ });
299
+ });
300
+ });
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Tests for CachedWordPressClient
3
+ */
4
+
5
+ import { CachedWordPressClient } from '../index.js';
6
+ import type { WordPressClientConfig } from '../../types/client.js';
7
+
8
+ // Mock the base WordPress client
9
+ jest.mock('../../client/api.js', () => {
10
+ return {
11
+ WordPressClient: jest.fn().mockImplementation(() => ({
12
+ baseUrl: 'https://example.com',
13
+ auth: { method: 'app-password', username: 'test', appPassword: 'test' },
14
+ jwtToken: null,
15
+ request: jest.fn(),
16
+ getPosts: jest.fn(),
17
+ getPost: jest.fn(),
18
+ createPost: jest.fn(),
19
+ updatePost: jest.fn(),
20
+ deletePost: jest.fn(),
21
+ getCurrentUser: jest.fn(),
22
+ getCategories: jest.fn(),
23
+ getTags: jest.fn(),
24
+ getSiteSettings: jest.fn()
25
+ }))
26
+ };
27
+ });
28
+
29
+ describe('CachedWordPressClient', () => {
30
+ let client: CachedWordPressClient;
31
+ let config: WordPressClientConfig;
32
+
33
+ beforeEach(() => {
34
+ config = {
35
+ baseUrl: 'https://example.com',
36
+ auth: {
37
+ method: 'app-password',
38
+ username: 'test',
39
+ appPassword: 'test-password'
40
+ }
41
+ };
42
+
43
+ client = new CachedWordPressClient(config, 'test-site');
44
+ });
45
+
46
+ afterEach(() => {
47
+ client.clearCache();
48
+ jest.clearAllMocks();
49
+ });
50
+
51
+ describe('Initialization', () => {
52
+ test('should initialize with caching system', () => {
53
+ expect(client).toBeDefined();
54
+ expect(client['cacheManager']).toBeDefined();
55
+ expect(client['httpCache']).toBeDefined();
56
+ expect(client['cacheInvalidation']).toBeDefined();
57
+ });
58
+
59
+ test('should use provided site ID', () => {
60
+ const customClient = new CachedWordPressClient(config, 'custom-site');
61
+ expect(customClient['siteId']).toBe('custom-site');
62
+ });
63
+
64
+ test('should default to "default" site ID', () => {
65
+ const defaultClient = new CachedWordPressClient(config);
66
+ expect(defaultClient['siteId']).toBe('default');
67
+ });
68
+ });
69
+
70
+ describe('GET Request Caching', () => {
71
+ test('should cache GET requests', async () => {
72
+ const mockResponse = [{ id: 1, title: 'Test Post' }];
73
+ (client as any).request = jest.fn()
74
+ .mockImplementationOnce(async () => mockResponse)
75
+ .mockImplementationOnce(async () => mockResponse);
76
+
77
+ // First call should hit the API
78
+ const result1 = await client.getPosts();
79
+ expect(result1).toEqual(mockResponse);
80
+
81
+ // Second call should use cache (mocked request should only be called once)
82
+ const result2 = await client.getPosts();
83
+ expect(result2).toEqual(mockResponse);
84
+
85
+ // Verify the underlying request was only called once due to caching
86
+ expect((client as any).request).toHaveBeenCalledTimes(1);
87
+ });
88
+
89
+ test('should cache individual post requests', async () => {
90
+ const mockPost = { id: 1, title: 'Test Post' };
91
+ (client as any).request = jest.fn().mockResolvedValue(mockPost);
92
+
93
+ const result1 = await client.getPost(1);
94
+ const result2 = await client.getPost(1);
95
+
96
+ expect(result1).toEqual(mockPost);
97
+ expect(result2).toEqual(mockPost);
98
+ expect((client as any).request).toHaveBeenCalledTimes(1);
99
+ });
100
+ });
101
+
102
+ describe('Write Operations and Cache Invalidation', () => {
103
+ test('should invalidate cache on post creation', async () => {
104
+ const mockPost = { id: 1, title: 'New Post' };
105
+ const mockCreateData = { title: 'New Post', content: 'Content' };
106
+
107
+ // Mock the parent class methods
108
+ const _originalCreatePost = client.createPost;
109
+ client.createPost = jest.fn().mockResolvedValue(mockPost);
110
+
111
+ const invalidateSpy = jest.spyOn(client['cacheInvalidation'], 'invalidateResource');
112
+
113
+ await client.createPost(mockCreateData);
114
+
115
+ expect(invalidateSpy).toHaveBeenCalledWith('posts', 1, 'create');
116
+ });
117
+
118
+ test('should invalidate cache on post update', async () => {
119
+ const mockPost = { id: 1, title: 'Updated Post' };
120
+ const mockUpdateData = { id: 1, title: 'Updated Post' };
121
+
122
+ client.updatePost = jest.fn().mockResolvedValue(mockPost);
123
+ const invalidateSpy = jest.spyOn(client['cacheInvalidation'], 'invalidateResource');
124
+
125
+ await client.updatePost(mockUpdateData);
126
+
127
+ expect(invalidateSpy).toHaveBeenCalledWith('posts', 1, 'update');
128
+ });
129
+
130
+ test('should invalidate cache on post deletion', async () => {
131
+ client.deletePost = jest.fn().mockResolvedValue(undefined);
132
+ const invalidateSpy = jest.spyOn(client['cacheInvalidation'], 'invalidateResource');
133
+
134
+ await client.deletePost(1);
135
+
136
+ expect(invalidateSpy).toHaveBeenCalledWith('posts', 1, 'delete');
137
+ });
138
+ });
139
+
140
+ describe('Cache Configuration by Endpoint Type', () => {
141
+ test('should use static caching for site settings', async () => {
142
+ const mockSettings = { title: 'Test Site' };
143
+ (client as any).request = jest.fn().mockResolvedValue(mockSettings);
144
+
145
+ await client.getSiteSettings();
146
+
147
+ // Verify request was made with static cache configuration
148
+ expect((client as any).request).toHaveBeenCalledWith(
149
+ 'GET',
150
+ 'settings'
151
+ );
152
+ });
153
+
154
+ test('should use semi-static caching for categories', async () => {
155
+ const mockCategories = [{ id: 1, name: 'Test Category' }];
156
+ (client as any).request = jest.fn().mockResolvedValue(mockCategories);
157
+
158
+ await client.getCategories();
159
+
160
+ expect((client as any).request).toHaveBeenCalledWith(
161
+ 'GET',
162
+ 'categories',
163
+ null,
164
+ { params: {} }
165
+ );
166
+ });
167
+
168
+ test('should use session caching for current user', async () => {
169
+ const mockUser = { id: 1, username: 'testuser' };
170
+ (client as any).request = jest.fn().mockResolvedValue(mockUser);
171
+
172
+ await client.getCurrentUser();
173
+
174
+ expect((client as any).request).toHaveBeenCalledWith(
175
+ 'GET',
176
+ 'users/me'
177
+ );
178
+ });
179
+ });
180
+
181
+ describe('Cache Management', () => {
182
+ test('should clear all cache', () => {
183
+ const clearSpy = jest.spyOn(client['httpCache'], 'invalidateAll');
184
+
185
+ const result = client.clearCache();
186
+
187
+ expect(clearSpy).toHaveBeenCalled();
188
+ expect(typeof result).toBe('number');
189
+ });
190
+
191
+ test('should clear cache by pattern', () => {
192
+ const pattern = 'posts.*';
193
+ const clearPatternSpy = jest.spyOn(client['httpCache'], 'invalidatePattern');
194
+
195
+ const result = client.clearCachePattern(pattern);
196
+
197
+ expect(clearPatternSpy).toHaveBeenCalledWith(pattern);
198
+ expect(typeof result).toBe('number');
199
+ });
200
+
201
+ test('should provide cache statistics', () => {
202
+ const stats = client.getCacheStats();
203
+
204
+ expect(stats).toHaveProperty('cache');
205
+ expect(stats).toHaveProperty('invalidation');
206
+ expect(stats.cache).toHaveProperty('hits');
207
+ expect(stats.cache).toHaveProperty('misses');
208
+ expect(stats.cache).toHaveProperty('totalSize');
209
+ expect(stats.invalidation).toHaveProperty('queueSize');
210
+ });
211
+
212
+ test('should warm cache with essential data', async () => {
213
+ // Mock all the methods that warmCache calls
214
+ client.getCurrentUser = jest.fn().mockResolvedValue({ id: 1 });
215
+ client.getCategories = jest.fn().mockResolvedValue([]);
216
+ client.getTags = jest.fn().mockResolvedValue([]);
217
+ client.getSiteSettings = jest.fn().mockResolvedValue({});
218
+
219
+ await client.warmCache();
220
+
221
+ expect(client.getCurrentUser).toHaveBeenCalled();
222
+ expect(client.getCategories).toHaveBeenCalled();
223
+ expect(client.getTags).toHaveBeenCalled();
224
+ expect(client.getSiteSettings).toHaveBeenCalled();
225
+ });
226
+
227
+ test('should handle cache warming errors gracefully', async () => {
228
+ client.getCurrentUser = jest.fn().mockRejectedValue(new Error('Auth failed'));
229
+ client.getCategories = jest.fn().mockResolvedValue([]);
230
+ client.getTags = jest.fn().mockResolvedValue([]);
231
+ client.getSiteSettings = jest.fn().mockResolvedValue({});
232
+
233
+ // Should not throw despite getCurrentUser failing
234
+ await expect(client.warmCache()).resolves.toBeUndefined();
235
+
236
+ expect(client.getCategories).toHaveBeenCalled();
237
+ expect(client.getTags).toHaveBeenCalled();
238
+ expect(client.getSiteSettings).toHaveBeenCalled();
239
+ });
240
+ });
241
+
242
+ describe('Cache Key Generation', () => {
243
+ test('should generate different cache keys for different parameters', async () => {
244
+ const mockPosts = [{ id: 1 }];
245
+ (client as any).request = jest.fn().mockResolvedValue(mockPosts);
246
+
247
+ // Make requests with different parameters
248
+ await client.getPosts({ per_page: 10 });
249
+ await client.getPosts({ per_page: 20 });
250
+
251
+ // Should make two separate requests due to different cache keys
252
+ expect((client as any).request).toHaveBeenCalledTimes(2);
253
+ });
254
+
255
+ test('should use same cache key for identical parameters', async () => {
256
+ const mockPosts = [{ id: 1 }];
257
+ (client as any).request = jest.fn().mockResolvedValue(mockPosts);
258
+
259
+ const params = { per_page: 10, status: ['publish'] as ['publish'] };
260
+
261
+ await client.getPosts(params);
262
+ await client.getPosts(params);
263
+
264
+ // Should only make one request due to caching
265
+ expect((client as any).request).toHaveBeenCalledTimes(1);
266
+ });
267
+ });
268
+
269
+ describe('Helper Methods', () => {
270
+ test('should extract resource from endpoint', () => {
271
+ const resource1 = (client as any).extractResourceFromEndpoint('posts');
272
+ const resource2 = (client as any).extractResourceFromEndpoint('posts/123');
273
+ const resource3 = (client as any).extractResourceFromEndpoint('categories');
274
+
275
+ expect(resource1).toBe('posts');
276
+ expect(resource2).toBe('posts');
277
+ expect(resource3).toBe('categories');
278
+ });
279
+
280
+ test('should extract ID from endpoint', () => {
281
+ const id1 = (client as any).extractIdFromEndpoint('posts/123');
282
+ const id2 = (client as any).extractIdFromEndpoint('posts/123/revisions');
283
+ const id3 = (client as any).extractIdFromEndpoint('posts');
284
+
285
+ expect(id1).toBe(123);
286
+ expect(id2).toBe(123);
287
+ expect(id3).toBeUndefined();
288
+ });
289
+
290
+ test('should identify endpoint types correctly', () => {
291
+ expect((client as any).isStaticEndpoint('settings')).toBe(true);
292
+ expect((client as any).isStaticEndpoint('types')).toBe(true);
293
+ expect((client as any).isStaticEndpoint('posts')).toBe(false);
294
+
295
+ expect((client as any).isSemiStaticEndpoint('categories')).toBe(true);
296
+ expect((client as any).isSemiStaticEndpoint('tags')).toBe(true);
297
+ expect((client as any).isSemiStaticEndpoint('posts')).toBe(false);
298
+
299
+ expect((client as any).isSessionEndpoint('users/me')).toBe(true);
300
+ expect((client as any).isSessionEndpoint('application-passwords')).toBe(true);
301
+ expect((client as any).isSessionEndpoint('posts')).toBe(false);
302
+ });
303
+ });
304
+ });