All files / src/entities/access_tokens schema-readonly.ts

100% Statements 12/12
100% Branches 4/4
100% Functions 1/1
100% Lines 12/12

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 8917x 17x                         17x     17x     17x           17x     17x                                 17x               17x               17x                       17x               8x            
import { z } from 'zod';
import { requiredId, paginationFields, flexibleBoolean } from '../utils';
 
// ============================================================================
// browse_access_tokens - CQRS Query Tool (discriminated union schema)
// Actions: list_personal, list_project, list_group, get
//
// Access tokens (personal / project / group) are CI and automation credentials.
// GitLab exposes them only through REST (no GraphQL surface), so all actions map
// to the /personal_access_tokens, /projects/:id/access_tokens and
// /groups/:id/access_tokens endpoints. The three scopes fold in as actions, not
// separate tools. Gated behind USE_ACCESS_TOKENS. Free tier.
// ============================================================================
 
export const projectIdField = requiredId.describe(
  "Project ID or URL-encoded path (e.g. 'group/project' or '123').",
);
export const groupIdField = requiredId.describe(
  "Group ID or URL-encoded path (e.g. 'my-group' or '42').",
);
export const tokenIdField = z.coerce
  .number()
  .int()
  .positive()
  .describe('Numeric access-token ID (from a list action).');
 
const stateFilter = z.enum(['active', 'inactive']).optional().describe('Filter by token state.');
 
// --- Action: list_personal (the current user's PATs; admin can pass user_id) ---
const ListPersonalSchema = z.object({
  action: z
    .literal('list_personal')
    .describe('List personal access tokens for the current user (admins may filter by user_id)'),
  user_id: z.coerce
    .number()
    .int()
    .positive()
    .optional()
    .describe('Admin only: list PATs belonging to this user ID.'),
  revoked: flexibleBoolean.optional().describe('Filter by revoked state.'),
  state: stateFilter,
  search: z.string().optional().describe('Filter by token name substring.'),
  ...paginationFields(),
});
 
// --- Action: list_project ---
const ListProjectSchema = z.object({
  action: z.literal('list_project').describe('List a project access tokens'),
  project_id: projectIdField,
  state: stateFilter,
  ...paginationFields(),
});
 
// --- Action: list_group ---
const ListGroupSchema = z.object({
  action: z.literal('list_group').describe('List a group access tokens'),
  group_id: groupIdField,
  state: stateFilter,
  ...paginationFields(),
});
 
// --- Action: get (single token; scope inferred from project_id/group_id) ---
const GetTokenSchema = z.object({
  action: z
    .literal('get')
    .describe(
      'Get a single access token by ID. Pass project_id for a project token, group_id for a group token, or neither for a personal token.',
    ),
  token_id: tokenIdField,
  project_id: requiredId.optional().describe('Set for a project access token.'),
  group_id: requiredId.optional().describe('Set for a group access token.'),
});
 
// --- Discriminated union combining all actions ---
export const BrowseAccessTokensSchema = z
  .discriminatedUnion('action', [
    ListPersonalSchema,
    ListProjectSchema,
    ListGroupSchema,
    GetTokenSchema,
  ])
  // A token is owned by exactly one scope; project_id and group_id are mutually exclusive.
  .refine((data) => data.action !== 'get' || !(data.project_id && data.group_id), {
    message: 'Pass at most one of project_id or group_id (a token belongs to a single scope)',
    path: ['project_id'],
  });
 
export type BrowseAccessTokensInput = z.infer<typeof BrowseAccessTokensSchema>;