1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
2//
3// SPDX-License-Identifier: AGPL-3.0-or-later
4
5package mcp
6
7import (
8 "strings"
9 "testing"
10
11 "git.secluded.site/planning-mcp-server/internal/config"
12)
13
14func TestPlanningValidator_ValidateSetGoalRequest(t *testing.T) {
15 cfg := &config.Config{
16 Planning: config.PlanningConfig{
17 MaxGoalLength: 100,
18 },
19 }
20 validator := NewPlanningValidator(cfg)
21
22 tests := []struct {
23 name string
24 req SetGoalRequest
25 wantErr bool
26 errMsg string
27 }{
28 {
29 name: "valid request",
30 req: SetGoalRequest{
31 Title: "Test Goal",
32 Description: "Valid description",
33 },
34 wantErr: false,
35 },
36 {
37 name: "empty title",
38 req: SetGoalRequest{
39 Title: "",
40 Description: "Valid description",
41 },
42 wantErr: true,
43 errMsg: "title is required",
44 },
45 {
46 name: "empty description",
47 req: SetGoalRequest{
48 Title: "Valid title",
49 Description: "",
50 },
51 wantErr: true,
52 errMsg: "description is required",
53 },
54 {
55 name: "title too long",
56 req: SetGoalRequest{
57 Title: strings.Repeat("x", 101),
58 Description: "Valid description",
59 },
60 wantErr: true,
61 errMsg: "title too long",
62 },
63 {
64 name: "description too long",
65 req: SetGoalRequest{
66 Title: "Valid title",
67 Description: strings.Repeat("x", 101),
68 },
69 wantErr: true,
70 errMsg: "description too long",
71 },
72 {
73 name: "title at max length",
74 req: SetGoalRequest{
75 Title: strings.Repeat("x", 100),
76 Description: "Valid description",
77 },
78 wantErr: false,
79 },
80 }
81
82 for _, tt := range tests {
83 t.Run(tt.name, func(t *testing.T) {
84 err := validator.ValidateSetGoalRequest(tt.req)
85 if tt.wantErr {
86 if err == nil {
87 t.Errorf("ValidateSetGoalRequest() expected error, got nil")
88 return
89 }
90 if !strings.Contains(err.Error(), tt.errMsg) {
91 t.Errorf("ValidateSetGoalRequest() error = %v, want error containing %v", err, tt.errMsg)
92 }
93 } else {
94 if err != nil {
95 t.Errorf("ValidateSetGoalRequest() error = %v, want nil", err)
96 }
97 }
98 })
99 }
100}
101
102func TestPlanningValidator_ValidateChangeGoalRequest(t *testing.T) {
103 cfg := &config.Config{
104 Planning: config.PlanningConfig{
105 MaxGoalLength: 50,
106 },
107 }
108 validator := NewPlanningValidator(cfg)
109
110 tests := []struct {
111 name string
112 req ChangeGoalRequest
113 wantErr bool
114 errMsg string
115 }{
116 {
117 name: "valid request",
118 req: ChangeGoalRequest{
119 Title: "New Goal",
120 Description: "New description",
121 Reason: "Valid reason",
122 },
123 wantErr: false,
124 },
125 {
126 name: "empty title",
127 req: ChangeGoalRequest{
128 Title: "",
129 Description: "Valid description",
130 Reason: "Valid reason",
131 },
132 wantErr: true,
133 errMsg: "title is required",
134 },
135 {
136 name: "empty description",
137 req: ChangeGoalRequest{
138 Title: "Valid title",
139 Description: "",
140 Reason: "Valid reason",
141 },
142 wantErr: true,
143 errMsg: "description is required",
144 },
145 {
146 name: "empty reason",
147 req: ChangeGoalRequest{
148 Title: "Valid title",
149 Description: "Valid description",
150 Reason: "",
151 },
152 wantErr: true,
153 errMsg: "reason is required",
154 },
155 {
156 name: "reason too long",
157 req: ChangeGoalRequest{
158 Title: "Valid title",
159 Description: "Valid description",
160 Reason: strings.Repeat("x", 51),
161 },
162 wantErr: true,
163 errMsg: "reason too long",
164 },
165 }
166
167 for _, tt := range tests {
168 t.Run(tt.name, func(t *testing.T) {
169 err := validator.ValidateChangeGoalRequest(tt.req)
170 if tt.wantErr {
171 if err == nil {
172 t.Errorf("ValidateChangeGoalRequest() expected error, got nil")
173 return
174 }
175 if !strings.Contains(err.Error(), tt.errMsg) {
176 t.Errorf("ValidateChangeGoalRequest() error = %v, want error containing %v", err, tt.errMsg)
177 }
178 } else {
179 if err != nil {
180 t.Errorf("ValidateChangeGoalRequest() error = %v, want nil", err)
181 }
182 }
183 })
184 }
185}
186
187func TestPlanningValidator_ValidateAddTasksRequest(t *testing.T) {
188 cfg := &config.Config{
189 Planning: config.PlanningConfig{
190 MaxTaskLength: 50,
191 },
192 }
193 validator := NewPlanningValidator(cfg)
194
195 tests := []struct {
196 name string
197 req AddTasksRequest
198 wantErr bool
199 errMsg string
200 }{
201 {
202 name: "valid single task",
203 req: AddTasksRequest{
204 Tasks: []MCPTaskInput{
205 {Title: "Valid task", Description: "Valid description"},
206 },
207 },
208 wantErr: false,
209 },
210 {
211 name: "valid multiple tasks",
212 req: AddTasksRequest{
213 Tasks: []MCPTaskInput{
214 {Title: "Task 1", Description: "Description 1"},
215 {Title: "Task 2", Description: ""},
216 {Title: "Task 3", Description: "Description 3"},
217 },
218 },
219 wantErr: false,
220 },
221 {
222 name: "empty tasks array",
223 req: AddTasksRequest{
224 Tasks: []MCPTaskInput{},
225 },
226 wantErr: true,
227 errMsg: "at least one task is required",
228 },
229 {
230 name: "task with empty title",
231 req: AddTasksRequest{
232 Tasks: []MCPTaskInput{
233 {Title: "", Description: "Valid description"},
234 },
235 },
236 wantErr: true,
237 errMsg: "task 0: title is required",
238 },
239 {
240 name: "task with title too long",
241 req: AddTasksRequest{
242 Tasks: []MCPTaskInput{
243 {Title: strings.Repeat("x", 51), Description: "Valid description"},
244 },
245 },
246 wantErr: true,
247 errMsg: "task 0: title too long",
248 },
249 {
250 name: "task with description too long",
251 req: AddTasksRequest{
252 Tasks: []MCPTaskInput{
253 {Title: "Valid title", Description: strings.Repeat("x", 51)},
254 },
255 },
256 wantErr: true,
257 errMsg: "task 0: description too long",
258 },
259 {
260 name: "second task invalid",
261 req: AddTasksRequest{
262 Tasks: []MCPTaskInput{
263 {Title: "Valid task", Description: "Valid description"},
264 {Title: "", Description: "Another description"},
265 },
266 },
267 wantErr: true,
268 errMsg: "task 1: title is required",
269 },
270 }
271
272 for _, tt := range tests {
273 t.Run(tt.name, func(t *testing.T) {
274 err := validator.ValidateAddTasksRequest(tt.req)
275 if tt.wantErr {
276 if err == nil {
277 t.Errorf("ValidateAddTasksRequest() expected error, got nil")
278 return
279 }
280 if !strings.Contains(err.Error(), tt.errMsg) {
281 t.Errorf("ValidateAddTasksRequest() error = %v, want error containing %v", err, tt.errMsg)
282 }
283 } else {
284 if err != nil {
285 t.Errorf("ValidateAddTasksRequest() error = %v, want nil", err)
286 }
287 }
288 })
289 }
290}
291
292func TestPlanningValidator_ValidateGetTasksRequest(t *testing.T) {
293 cfg := &config.Config{
294 Planning: config.PlanningConfig{},
295 }
296 validator := NewPlanningValidator(cfg)
297
298 tests := []struct {
299 name string
300 req GetTasksRequest
301 wantErr bool
302 errMsg string
303 }{
304 {
305 name: "empty status (valid)",
306 req: GetTasksRequest{Status: ""},
307 wantErr: false,
308 },
309 {
310 name: "valid status all",
311 req: GetTasksRequest{Status: "all"},
312 wantErr: false,
313 },
314 {
315 name: "valid status pending",
316 req: GetTasksRequest{Status: "pending"},
317 wantErr: false,
318 },
319 {
320 name: "valid status in_progress",
321 req: GetTasksRequest{Status: "in_progress"},
322 wantErr: false,
323 },
324 {
325 name: "valid status completed",
326 req: GetTasksRequest{Status: "completed"},
327 wantErr: false,
328 },
329 {
330 name: "valid status cancelled",
331 req: GetTasksRequest{Status: "cancelled"},
332 wantErr: false,
333 },
334 {
335 name: "valid status failed",
336 req: GetTasksRequest{Status: "failed"},
337 wantErr: false,
338 },
339 {
340 name: "invalid status",
341 req: GetTasksRequest{Status: "invalid"},
342 wantErr: true,
343 errMsg: "invalid status 'invalid'",
344 },
345 {
346 name: "invalid status case sensitive",
347 req: GetTasksRequest{Status: "Pending"},
348 wantErr: true,
349 errMsg: "invalid status 'Pending'",
350 },
351 }
352
353 for _, tt := range tests {
354 t.Run(tt.name, func(t *testing.T) {
355 err := validator.ValidateGetTasksRequest(tt.req)
356 if tt.wantErr {
357 if err == nil {
358 t.Errorf("ValidateGetTasksRequest() expected error, got nil")
359 return
360 }
361 if !strings.Contains(err.Error(), tt.errMsg) {
362 t.Errorf("ValidateGetTasksRequest() error = %v, want error containing %v", err, tt.errMsg)
363 }
364 } else {
365 if err != nil {
366 t.Errorf("ValidateGetTasksRequest() error = %v, want nil", err)
367 }
368 }
369 })
370 }
371}
372
373func TestPlanningValidator_ValidateUpdateTaskStatusesRequest(t *testing.T) {
374 cfg := &config.Config{
375 Planning: config.PlanningConfig{},
376 }
377 validator := NewPlanningValidator(cfg)
378
379 tests := []struct {
380 name string
381 req UpdateTaskStatusesRequest
382 wantErr bool
383 errMsg string
384 }{
385 {
386 name: "valid single update",
387 req: UpdateTaskStatusesRequest{
388 Tasks: []MCPTaskUpdateInput{
389 {TaskID: "task1", Status: "completed"},
390 },
391 },
392 wantErr: false,
393 },
394 {
395 name: "valid multiple updates",
396 req: UpdateTaskStatusesRequest{
397 Tasks: []MCPTaskUpdateInput{
398 {TaskID: "task1", Status: "completed"},
399 {TaskID: "task2", Status: "in_progress"},
400 {TaskID: "task3", Status: "failed"},
401 },
402 },
403 wantErr: false,
404 },
405 {
406 name: "empty updates array",
407 req: UpdateTaskStatusesRequest{
408 Tasks: []MCPTaskUpdateInput{},
409 },
410 wantErr: true,
411 errMsg: "at least one task update is required",
412 },
413 {
414 name: "empty task ID",
415 req: UpdateTaskStatusesRequest{
416 Tasks: []MCPTaskUpdateInput{
417 {TaskID: "", Status: "completed"},
418 },
419 },
420 wantErr: true,
421 errMsg: "task update 0: task_id is required",
422 },
423 {
424 name: "empty status",
425 req: UpdateTaskStatusesRequest{
426 Tasks: []MCPTaskUpdateInput{
427 {TaskID: "task1", Status: ""},
428 },
429 },
430 wantErr: true,
431 errMsg: "task update 0: status is required",
432 },
433 {
434 name: "invalid status",
435 req: UpdateTaskStatusesRequest{
436 Tasks: []MCPTaskUpdateInput{
437 {TaskID: "task1", Status: "invalid"},
438 },
439 },
440 wantErr: true,
441 errMsg: "task update 0: invalid status 'invalid'",
442 },
443 {
444 name: "second update invalid",
445 req: UpdateTaskStatusesRequest{
446 Tasks: []MCPTaskUpdateInput{
447 {TaskID: "task1", Status: "completed"},
448 {TaskID: "", Status: "pending"},
449 },
450 },
451 wantErr: true,
452 errMsg: "task update 1: task_id is required",
453 },
454 }
455
456 for _, tt := range tests {
457 t.Run(tt.name, func(t *testing.T) {
458 err := validator.ValidateUpdateTaskStatusesRequest(tt.req)
459 if tt.wantErr {
460 if err == nil {
461 t.Errorf("ValidateUpdateTaskStatusesRequest() expected error, got nil")
462 return
463 }
464 if !strings.Contains(err.Error(), tt.errMsg) {
465 t.Errorf("ValidateUpdateTaskStatusesRequest() error = %v, want error containing %v", err, tt.errMsg)
466 }
467 } else {
468 if err != nil {
469 t.Errorf("ValidateUpdateTaskStatusesRequest() error = %v, want nil", err)
470 }
471 }
472 })
473 }
474}
475
476func TestPlanningValidator_ValidateDeleteTasksRequest(t *testing.T) {
477 cfg := &config.Config{
478 Planning: config.PlanningConfig{},
479 }
480 validator := NewPlanningValidator(cfg)
481
482 tests := []struct {
483 name string
484 req DeleteTasksRequest
485 wantErr bool
486 errMsg string
487 }{
488 {
489 name: "valid single task ID",
490 req: DeleteTasksRequest{
491 TaskIDs: []string{"task1"},
492 },
493 wantErr: false,
494 },
495 {
496 name: "valid multiple task IDs",
497 req: DeleteTasksRequest{
498 TaskIDs: []string{"task1", "task2", "task3"},
499 },
500 wantErr: false,
501 },
502 {
503 name: "empty task IDs array",
504 req: DeleteTasksRequest{
505 TaskIDs: []string{},
506 },
507 wantErr: true,
508 errMsg: "at least one task ID is required",
509 },
510 {
511 name: "empty task ID",
512 req: DeleteTasksRequest{
513 TaskIDs: []string{""},
514 },
515 wantErr: true,
516 errMsg: "task ID 0 is empty",
517 },
518 {
519 name: "second task ID empty",
520 req: DeleteTasksRequest{
521 TaskIDs: []string{"task1", ""},
522 },
523 wantErr: true,
524 errMsg: "task ID 1 is empty",
525 },
526 }
527
528 for _, tt := range tests {
529 t.Run(tt.name, func(t *testing.T) {
530 err := validator.ValidateDeleteTasksRequest(tt.req)
531 if tt.wantErr {
532 if err == nil {
533 t.Errorf("ValidateDeleteTasksRequest() expected error, got nil")
534 return
535 }
536 if !strings.Contains(err.Error(), tt.errMsg) {
537 t.Errorf("ValidateDeleteTasksRequest() error = %v, want error containing %v", err, tt.errMsg)
538 }
539 } else {
540 if err != nil {
541 t.Errorf("ValidateDeleteTasksRequest() error = %v, want nil", err)
542 }
543 }
544 })
545 }
546}
547
548func TestNewPlanningValidator(t *testing.T) {
549 cfg := &config.Config{
550 Planning: config.PlanningConfig{
551 MaxGoalLength: 100,
552 MaxTaskLength: 200,
553 },
554 }
555
556 validator := NewPlanningValidator(cfg)
557
558 if validator == nil {
559 t.Error("NewPlanningValidator() returned nil")
560 return
561 }
562
563 if validator.config != cfg {
564 t.Error("NewPlanningValidator() did not set config correctly")
565 }
566}