Skip to content

Commit b909916

Browse files
committed
feat(core): add todos data lifecycle tests and address Copilot review feedback
TODOS INTEGRATION TESTING: - Add todos helper methods to registry-helper.ts (listTodos, markTodoDone, etc.) - Add Step 6.5 to data-lifecycle.test.ts for todos infrastructure testing - Tests: list pending todos, filter by type, mark_done/restore, mark_all_done COPILOT REVIEW FIXES: - Document fork_name/fork_path rationale (distinct from create's name for schema) - Clarify manage_todos response types in description (object vs success status) - Clarify q vs search parameter distinction in BrowseProjectsSchema - Improve handler code with explicit destructuring per action OAUTH TEST COVERAGE: - Add callback.test.ts for OAuth callback endpoint - Add gitlab-device-flow.test.ts for device flow operations
1 parent b2dc398 commit b909916

File tree

15 files changed

+3367
-2032
lines changed

15 files changed

+3367
-2032
lines changed

src/entities/core/registry.ts

Lines changed: 245 additions & 207 deletions
Large diffs are not rendered by default.

src/entities/core/schema-readonly.ts

Lines changed: 147 additions & 342 deletions
Large diffs are not rendered by default.

src/entities/core/schema.ts

Lines changed: 59 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,30 @@ import { flexibleBoolean } from "../utils";
33

44
// ============================================================================
55
// CONSOLIDATED WRITE SCHEMAS (Issue #16)
6-
// Future: Consider Zod discriminated unions for compile-time action validation
6+
// Using Zod discriminated unions for compile-time action validation
77
// ============================================================================
88

9-
// manage_repository: Consolidates create_repository, fork_repository (2 → 1)
10-
export const ManageRepositorySchema = z.object({
11-
action: z
12-
.enum(["create", "fork"])
13-
.describe("Operation: create=new repository, fork=copy existing repository."),
9+
// Common options shared between create and fork actions
10+
const CommonRepositoryOptions = {
11+
issues_enabled: flexibleBoolean.optional().describe("Enable issue tracking."),
12+
merge_requests_enabled: flexibleBoolean.optional().describe("Enable merge requests."),
13+
jobs_enabled: flexibleBoolean.optional().describe("Enable CI/CD jobs."),
14+
wiki_enabled: flexibleBoolean.optional().describe("Enable project wiki."),
15+
snippets_enabled: flexibleBoolean.optional().describe("Enable code snippets."),
16+
lfs_enabled: flexibleBoolean.optional().describe("Enable Git LFS."),
17+
request_access_enabled: flexibleBoolean.optional().describe("Allow access requests."),
18+
only_allow_merge_if_pipeline_succeeds: flexibleBoolean
19+
.optional()
20+
.describe("Require passing pipelines for merge."),
21+
only_allow_merge_if_all_discussions_are_resolved: flexibleBoolean
22+
.optional()
23+
.describe("Require resolved discussions for merge."),
24+
};
1425

15-
// For "create" action
16-
name: z.string().optional().describe('Project name (required for "create" action).'),
26+
// manage_repository: Discriminated union for create vs fork
27+
const CreateRepositoryAction = z.object({
28+
action: z.literal("create").describe("Create a new repository."),
29+
name: z.string().describe("Project name (required for create)."),
1730
namespace: z
1831
.string()
1932
.optional()
@@ -24,34 +37,35 @@ export const ManageRepositorySchema = z.object({
2437
.optional()
2538
.describe("Project visibility level."),
2639
initialize_with_readme: flexibleBoolean.optional().describe("Create initial README.md file."),
40+
...CommonRepositoryOptions,
41+
});
2742

28-
// For "fork" action
29-
// Note: fork_name/fork_path are distinct from create's name to avoid schema conflicts.
30-
// Handler maps these to GitLab API's 'name'/'path' params for fork endpoint.
43+
// fork_name/fork_path are distinct from create's name to avoid schema conflicts in the
44+
// discriminated union. Handler maps these to GitLab API's name/path parameters.
45+
const ForkRepositoryAction = z.object({
46+
action: z.literal("fork").describe("Fork an existing repository."),
3147
project_id: z.coerce
48+
.string()
49+
.describe("Source project to fork (required for fork). Numeric ID or URL-encoded path."),
50+
namespace: z.string().optional().describe("Target namespace ID or path for fork."),
51+
namespace_path: z.string().optional().describe("Target namespace path for fork."),
52+
fork_name: z
3253
.string()
3354
.optional()
34-
.describe('Source project to fork (required for "fork" action).'),
35-
namespace_path: z.string().optional().describe("Target namespace for fork."),
36-
fork_name: z.string().optional().describe("New name for forked project (maps to API 'name')."),
37-
fork_path: z.string().optional().describe("New path for forked project (maps to API 'path')."),
38-
39-
// Common creation options
40-
issues_enabled: flexibleBoolean.optional().describe("Enable issue tracking."),
41-
merge_requests_enabled: flexibleBoolean.optional().describe("Enable merge requests."),
42-
jobs_enabled: flexibleBoolean.optional().describe("Enable CI/CD jobs."),
43-
wiki_enabled: flexibleBoolean.optional().describe("Enable project wiki."),
44-
snippets_enabled: flexibleBoolean.optional().describe("Enable code snippets."),
45-
lfs_enabled: flexibleBoolean.optional().describe("Enable Git LFS."),
46-
request_access_enabled: flexibleBoolean.optional().describe("Allow access requests."),
47-
only_allow_merge_if_pipeline_succeeds: flexibleBoolean
48-
.optional()
49-
.describe("Require passing pipelines for merge."),
50-
only_allow_merge_if_all_discussions_are_resolved: flexibleBoolean
55+
.describe("New name for forked project (maps to API 'name' parameter)."),
56+
fork_path: z
57+
.string()
5158
.optional()
52-
.describe("Require resolved discussions for merge."),
59+
.describe("New path for forked project (maps to API 'path' parameter)."),
60+
...CommonRepositoryOptions,
5361
});
5462

63+
export const ManageRepositorySchema = z
64+
.discriminatedUnion("action", [CreateRepositoryAction, ForkRepositoryAction])
65+
.describe(
66+
"REPOSITORY MANAGEMENT: Create or fork GitLab projects. Use 'create' with name for new project. Use 'fork' with project_id to copy existing project."
67+
);
68+
5569
// ============================================================================
5670
// KEPT AS-IS WRITE SCHEMAS
5771
// ============================================================================
@@ -81,96 +95,35 @@ export const CreateGroupSchema = z.object({
8195
avatar: z.string().optional().describe("Group avatar URL."),
8296
});
8397

84-
// Todos management (write operations)
85-
export const ManageTodosSchema = z.object({
86-
action: z
87-
.enum(["mark_done", "mark_all_done", "restore"])
88-
.describe(
89-
"Action: mark_done=complete single todo, mark_all_done=complete all, restore=reopen completed."
90-
),
91-
id: z
92-
.number()
93-
.int()
94-
.positive()
95-
.optional()
96-
.describe("Todo ID (required for mark_done and restore)."),
98+
// manage_todos: Discriminated union for mark_done/mark_all_done/restore
99+
const MarkDoneTodoAction = z.object({
100+
action: z.literal("mark_done").describe("Mark a single todo as done."),
101+
id: z.number().int().positive().describe("Todo ID to mark as done (required)."),
97102
});
98103

99-
// ============================================================================
100-
// DEPRECATED WRITE SCHEMAS (kept for backward compatibility)
101-
// ============================================================================
102-
103-
// @deprecated Use ManageRepositorySchema with action: "create"
104-
export const CreateRepositorySchema = z.object({
105-
name: z.string().describe("Project display name."),
106-
namespace: z.string().optional().describe("Target namespace path."),
107-
description: z.string().optional().describe("Project description."),
108-
issues_enabled: flexibleBoolean.optional(),
109-
merge_requests_enabled: flexibleBoolean.optional(),
110-
jobs_enabled: flexibleBoolean.optional(),
111-
wiki_enabled: flexibleBoolean.optional(),
112-
snippets_enabled: flexibleBoolean.optional(),
113-
resolve_outdated_diff_discussions: flexibleBoolean.optional(),
114-
container_registry_enabled: flexibleBoolean.optional(),
115-
container_registry_access_level: z.enum(["disabled", "private", "enabled"]).optional(),
116-
shared_runners_enabled: flexibleBoolean.optional(),
117-
visibility: z.enum(["private", "internal", "public"]).optional(),
118-
import_url: z.string().optional(),
119-
public_jobs: flexibleBoolean.optional(),
120-
only_allow_merge_if_pipeline_succeeds: flexibleBoolean.optional(),
121-
allow_merge_on_skipped_pipeline: flexibleBoolean.optional(),
122-
only_allow_merge_if_all_discussions_are_resolved: flexibleBoolean.optional(),
123-
merge_method: z.enum(["merge", "rebase_merge", "ff"]).optional(),
124-
autoclose_referenced_issues: flexibleBoolean.optional(),
125-
suggestion_commit_message: z.string().optional(),
126-
remove_source_branch_after_merge: flexibleBoolean.optional(),
127-
lfs_enabled: flexibleBoolean.optional(),
128-
request_access_enabled: flexibleBoolean.optional(),
129-
tag_list: z.array(z.string()).optional(),
130-
printing_merge_request_link_enabled: flexibleBoolean.optional(),
131-
build_git_strategy: z.enum(["fetch", "clone"]).optional(),
132-
build_timeout: z.number().optional(),
133-
auto_cancel_pending_pipelines: z.enum(["disabled", "enabled"]).optional(),
134-
build_coverage_regex: z.string().optional(),
135-
ci_config_path: z.string().optional(),
136-
auto_devops_enabled: flexibleBoolean.optional(),
137-
auto_devops_deploy_strategy: z.enum(["continuous", "manual", "timed_incremental"]).optional(),
138-
repository_storage: z.string().optional(),
139-
approvals_before_merge: z.number().optional(),
140-
external_authorization_classification_label: z.string().optional(),
141-
mirror: flexibleBoolean.optional(),
142-
mirror_trigger_builds: flexibleBoolean.optional(),
143-
initialize_with_readme: flexibleBoolean.optional(),
144-
template_name: z.string().optional(),
145-
template_project_id: z.number().optional(),
146-
use_custom_template: flexibleBoolean.optional(),
147-
group_with_project_templates_id: z.number().optional(),
148-
packages_enabled: flexibleBoolean.optional(),
149-
service_desk_enabled: flexibleBoolean.optional(),
150-
compliance_frameworks: z.array(z.string()).optional(),
104+
const MarkAllDoneTodoAction = z.object({
105+
action: z.literal("mark_all_done").describe("Mark all todos as done."),
151106
});
152107

153-
// @deprecated Use ManageRepositorySchema with action: "fork"
154-
export const ForkRepositorySchema = z.object({
155-
project_id: z.coerce.string().describe("Source project to fork."),
156-
namespace: z.string().optional().describe("Target namespace."),
157-
namespace_path: z.string().optional().describe("Target namespace path."),
158-
name: z.string().optional().describe("Fork name."),
159-
path: z.string().optional().describe("Fork path."),
108+
const RestoreTodoAction = z.object({
109+
action: z.literal("restore").describe("Restore a completed todo to pending."),
110+
id: z.number().int().positive().describe("Todo ID to restore (required)."),
160111
});
161112

113+
export const ManageTodosSchema = z
114+
.discriminatedUnion("action", [MarkDoneTodoAction, MarkAllDoneTodoAction, RestoreTodoAction])
115+
.describe(
116+
"TODO ACTIONS: Manage GitLab todo items. mark_done requires id, mark_all_done clears all, restore requires id."
117+
);
118+
162119
// ============================================================================
163120
// TYPE EXPORTS
164121
// ============================================================================
165122

166123
// Consolidated types
167124
export type ManageRepositoryOptions = z.infer<typeof ManageRepositorySchema>;
125+
export type ManageTodosOptions = z.infer<typeof ManageTodosSchema>;
168126

169127
// Kept as-is types
170128
export type CreateBranchOptions = z.infer<typeof CreateBranchSchema>;
171129
export type CreateGroupOptions = z.infer<typeof CreateGroupSchema>;
172-
export type ManageTodosOptions = z.infer<typeof ManageTodosSchema>;
173-
174-
// Deprecated types
175-
export type CreateRepositoryOptions = z.infer<typeof CreateRepositorySchema>;
176-
export type ForkRepositoryOptions = z.infer<typeof ForkRepositorySchema>;

0 commit comments

Comments
 (0)