/**
* Browse Projects Schema Integration Tests
* Tests BrowseProjectsSchema against real GitLab 18.3 API responses using handler functions
* Updated for CQRS consolidation (Issue #16) - uses discriminated union with action field
*/
import { BrowseProjectsSchema } from "../../../src/entities/core/schema-readonly";
import { GitLabProjectSchema } from "../../../src/entities/shared";
import { IntegrationTestHelper } from "../helpers/registry-helper";
describe("BrowseProjectsSchema - GitLab 18.3 Integration", () => {
let helper: IntegrationTestHelper;
beforeAll(async () => {
const GITLAB_TOKEN = process.env.GITLAB_TOKEN;
if (!GITLAB_TOKEN) {
throw new Error("GITLAB_TOKEN environment variable is required");
}
// Initialize integration test helper
helper = new IntegrationTestHelper();
await helper.initialize();
console.log("β
Integration test helper initialized for browse projects testing");
});
describe("action: list", () => {
it("should validate basic list projects parameters", async () => {
// Test basic parameters with action discriminator (CQRS consolidation - Issue #16)
const validParams = {
action: "list" as const,
per_page: 5,
page: 1,
order_by: "name" as const,
sort: "asc" as const,
visibility: "private" as const,
};
const result = BrowseProjectsSchema.safeParse(validParams);
expect(result.success).toBe(true);
if (result.success && result.data.action === "list") {
expect(result.data.per_page).toBe(5);
expect(result.data.order_by).toBe("name");
expect(result.data.sort).toBe("asc");
expect(result.data.visibility).toBe("private");
}
console.log("β
BrowseProjectsSchema validates basic list parameters correctly");
});
it("should make successful API request with validated parameters using handler function", async () => {
const params = {
action: "list" as const,
per_page: 3,
order_by: "last_activity_at" as const,
sort: "desc" as const,
};
console.log("π BrowseProjectsSchema - Testing list action with handler function");
// Validate parameters first
const paramResult = BrowseProjectsSchema.safeParse(params);
expect(paramResult.success).toBe(true);
if (paramResult.success) {
// Use handler function instead of direct API call
const projects = (await helper.executeTool("browse_projects", paramResult.data)) as any[];
console.log(`π Retrieved ${projects.length} projects via handler`);
expect(Array.isArray(projects)).toBe(true);
// Check what we actually got with simple=true
if (projects.length > 0) {
const firstProject = projects[0];
console.log("First project keys with simple=true:", Object.keys(firstProject));
// Since simple=true returns limited fields, let's just validate structure exists
expect(firstProject).toHaveProperty("id");
expect(firstProject).toHaveProperty("name");
expect(firstProject).toHaveProperty("path");
console.log(
` β
Project basic structure validated: ${firstProject.name} (ID: ${firstProject.id})`
);
}
console.log(
`β
BrowseProjectsSchema list action successful, validated ${projects.length} projects`
);
}
}, 15000);
it("should validate full project schema with simple=false", async () => {
console.log("π BrowseProjectsSchema - Testing full project schema validation");
const fullParams = {
action: "list" as const,
per_page: 2,
simple: false, // Override default to get full project data
};
const paramResult = BrowseProjectsSchema.safeParse(fullParams);
expect(paramResult.success).toBe(true);
if (paramResult.success) {
const projects = (await helper.executeTool("browse_projects", paramResult.data)) as any[];
console.log(`π Retrieved ${projects.length} full projects via handler`);
expect(Array.isArray(projects)).toBe(true);
// Validate that each project matches our GitLabProjectSchema
for (const project of projects.slice(0, 1)) {
// Test first project
const projectResult = GitLabProjectSchema.safeParse(project);
if (!projectResult.success) {
console.error("Full project validation failed for project:", project.id);
console.error("Error details:", JSON.stringify(projectResult.error.issues, null, 2));
console.error("Project data keys:", Object.keys(project).length);
}
expect(projectResult.success).toBe(true);
console.log(` β
Full project validated: ${project.name} (ID: ${project.id})`);
}
console.log(`β
BrowseProjectsSchema full validation successful`);
}
}, 15000);
it("should validate advanced filtering parameters", async () => {
const advancedParams = {
action: "list" as const,
archived: false,
membership: true,
per_page: 10,
};
const result = BrowseProjectsSchema.safeParse(advancedParams);
expect(result.success).toBe(true);
if (result.success && result.data.action === "list") {
expect(result.data.archived).toBe(false);
expect(result.data.membership).toBe(true);
}
console.log("β
BrowseProjectsSchema validates advanced filtering parameters");
});
it("should handle optional parameters correctly", async () => {
// Test with minimal required parameters (only action)
const minimalParams = {
action: "list" as const,
};
const result = BrowseProjectsSchema.safeParse(minimalParams);
expect(result.success).toBe(true);
// Test with optional values
const paramsWithOptional = {
action: "list" as const,
search: "test",
per_page: 5,
};
const resultWithOptional = BrowseProjectsSchema.safeParse(paramsWithOptional);
expect(resultWithOptional.success).toBe(true);
console.log("β
BrowseProjectsSchema handles optional parameters correctly");
});
it("should validate search functionality using handler function", async () => {
const searchParams = {
action: "list" as const,
search: "test",
per_page: 5,
};
console.log("π Testing search functionality with handler");
const result = BrowseProjectsSchema.safeParse(searchParams);
expect(result.success).toBe(true);
if (result.success) {
// Use handler function for search instead of direct API call
const projects = (await helper.executeTool("browse_projects", result.data)) as any[];
expect(Array.isArray(projects)).toBe(true);
console.log(
`β
Search via handler works, found ${projects.length} projects matching 'test'`
);
}
}, 15000);
});
describe("action: search", () => {
it("should validate search action parameters", async () => {
const searchParams = {
action: "search" as const,
q: "gitlab",
per_page: 10,
};
const result = BrowseProjectsSchema.safeParse(searchParams);
expect(result.success).toBe(true);
if (result.success && result.data.action === "search") {
expect(result.data.q).toBe("gitlab");
}
console.log("β
BrowseProjectsSchema validates search action parameters");
});
it("should search projects using handler function", async () => {
const searchParams = {
action: "search" as const,
q: "test",
per_page: 5,
};
console.log("π Testing search action with handler");
const result = BrowseProjectsSchema.safeParse(searchParams);
expect(result.success).toBe(true);
if (result.success) {
const projects = (await helper.executeTool("browse_projects", result.data)) as any[];
expect(Array.isArray(projects)).toBe(true);
console.log(`β
Search action works, found ${projects.length} projects`);
}
}, 15000);
});
describe("action: get", () => {
it("should validate get action parameters", async () => {
const getParams = {
action: "get" as const,
project_id: "123",
};
const result = BrowseProjectsSchema.safeParse(getParams);
expect(result.success).toBe(true);
if (result.success && result.data.action === "get") {
expect(result.data.project_id).toBe("123");
}
console.log("β
BrowseProjectsSchema validates get action parameters");
});
it("should reject missing project_id for get action", async () => {
// Note: requiredId properly rejects undefined/null values (Issue #27)
// This test verifies the schema correctly fails with missing project_id
const paramsWithMissingProjectId = {
action: "get" as const,
// missing project_id - should fail validation with requiredId
};
const result = BrowseProjectsSchema.safeParse(paramsWithMissingProjectId);
// With requiredId, missing field correctly fails validation
expect(result.success).toBe(false);
if (!result.success) {
// Verify the error is about project_id
expect(result.error.issues.some(issue => issue.path.includes("project_id"))).toBe(true);
}
console.log("β
BrowseProjectsSchema correctly rejects missing project_id");
});
it("should get specific project using handler function", async () => {
// First list projects to get a valid project_id
const listParams = {
action: "list" as const,
per_page: 1,
};
const projects = (await helper.executeTool("browse_projects", listParams)) as any[];
if (projects.length === 0) {
console.log("β οΈ No projects available for get action testing");
return;
}
const testProject = projects[0];
const getParams = {
action: "get" as const,
project_id: testProject.id.toString(),
statistics: true,
};
console.log("π Testing get action with handler");
const result = BrowseProjectsSchema.safeParse(getParams);
expect(result.success).toBe(true);
if (result.success) {
const project = (await helper.executeTool("browse_projects", result.data)) as any;
expect(project).toHaveProperty("id");
expect(project).toHaveProperty("name");
expect(project.id.toString()).toBe(testProject.id.toString());
console.log(`β
Get action works, retrieved project: ${project.name}`);
}
}, 15000);
});
describe("discriminated union validation", () => {
it("should require action field", async () => {
const paramsWithoutAction = {
per_page: 5,
};
const result = BrowseProjectsSchema.safeParse(paramsWithoutAction);
expect(result.success).toBe(false);
console.log("β
BrowseProjectsSchema correctly requires action field");
});
it("should reject invalid action values", async () => {
const invalidActionParams = {
action: "invalid" as any,
per_page: 5,
};
const result = BrowseProjectsSchema.safeParse(invalidActionParams);
expect(result.success).toBe(false);
console.log("β
BrowseProjectsSchema correctly rejects invalid action values");
});
it("should reject list-only parameters on search action", async () => {
// group_id is only valid for list action, not search
const searchWithListParams = {
action: "search" as const,
q: "test",
group_id: "123", // This should work - search doesn't have group_id but schema allows extra
};
// The discriminated union only validates the specific action's fields
const result = BrowseProjectsSchema.safeParse(searchWithListParams);
// Note: Zod's strict mode is not enabled, so extra fields are ignored
expect(result.success).toBe(true);
console.log("β
BrowseProjectsSchema handles action-specific parameters correctly");
});
});
});