# All files / src/clients/ep committeeClient.ts

95.12% Statements 39/41
77.14% Branches 27/35
88.88% Functions 8/9
94.73% Lines 36/38

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
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161

    344x

    32x

    14x
    14x
    14x

    4x
    4x

    1x

    14x
    14x
    10x
    10x

    4x

    4x

    4x

    4x

    4x
    4x
    4x
    3x

    1x

    14x
    14x
    14x
    14x
    13x
    13x

    1x

    1x

    7x
    7x

    7x

    6x
    18x
    7x
    7x

     /**
     * @fileoverview Committee sub-client for European Parliament API
     *
     * Handles committee (corporate body) information lookups including
     * direct lookup, list search, and current corporate bodies.
     *
     * @module clients/ep/committeeClient
     */

    import { auditLogger, toErrorMessage } from '../../utils/auditLogger.js';
    import type {
      Committee,
      PaginatedResponse,
    } from '../../types/europeanParliament.js';
    import {
      transformCorporateBody as _transformCorporateBody,
    } from './transformers.js';
    import {
      BaseEPClient,
      APIError,
      type EPClientConfig,
      type EPSharedResources,
      type JSONLDResponse,
    } from './baseClient.js';
    import { DEFAULT_TIMEOUTS } from '../../utils/timeout.js';

    /**
     * Sub-client for committee/corporate-body EP API endpoints.
     *
     * @extends BaseEPClient
     * @public
     */
    export class CommitteeClient extends BaseEPClient {
      constructor(config: EPClientConfig = {}, shared?: EPSharedResources) {
        super(config, shared);
      }

      private transformCorporateBody(apiData: Record<string, unknown>): Committee {
        return _transformCorporateBody(apiData);
      }

      /**
       * Resolves a committee by trying direct lookup then list search.
       * @throws {APIError} If committee not found
       * @private
       */
      private async resolveCommittee(searchTerm: string): Promise<Committee> {
        Eif (searchTerm !== '') {
          const directResult = await this.fetchCommitteeDirectly(searchTerm);
          if (directResult !== null) return directResult;
        }

        const found = await this.searchCommitteeInList(searchTerm);
        if (found !== null) return found;

        throw new APIError(`Committee not found: ${searchTerm || 'unknown'}`, 404);
      }

      /**
       * Attempts a direct corporate-body lookup by ID.
       * @private
       */
      private async fetchCommitteeDirectly(bodyId: string): Promise<Committee | null> {
        try {
          const response = await this.get<JSONLDResponse>(`corporate-bodies/${bodyId}`, {});
          Eif (response.data.length > 0) {
            return this.transformCorporateBody(response.data[0] ?? {});
          }
        } catch (error: unknown) {
          Iif (!(error instanceof APIError && error.statusCode === 404)) {
            auditLogger.logError('get_committee_info.fetch_direct', { bodyId }, toErrorMessage(error));
          }
        }
        return null;
      }

      /**
       * Searches the corporate-bodies list for a matching committee.
       * @private
       */
      private async searchCommitteeInList(searchTerm: string): Promise<Committee | null> {
        const listParams: Record<string, unknown> = {
          'body-classification': 'COMMITTEE_PARLIAMENTARY_STANDING',
          limit: 100,
        };
        const response = await this.get<JSONLDResponse>('corporate-bodies', listParams);

        for (const item of response.data) {
          const committee = this.transformCorporateBody(item);
          if (committee.abbreviation === searchTerm || committee.id === searchTerm) {
            return committee;
          }
        }
        return null;
      }

      /**
       * Retrieves committee (corporate body) information by ID or abbreviation.
       *
       * **EP API Endpoint:** `GET /corporate-bodies/{body-id}` or `GET /corporate-bodies`
       *
       * @param params - id or abbreviation of the committee
       * @returns Committee information
       * @security Audit logged per GDPR Article 30
       */
      async getCommitteeInfo(params: {
        id?: string;
        abbreviation?: string;
      }): Promise<Committee> {
        const action = 'get_committee_info';
        try {
          const searchTerm = params.abbreviation ?? params.id ?? '';
          const committee = await this.resolveCommittee(searchTerm);
          auditLogger.logDataAccess(action, params, 1);
          return committee;
        } catch (error: unknown) {
          auditLogger.logError(
            action,
            params,
            error instanceof Error ? error.message : 'Unknown error'
          );
          throw error;
        }
      }

      /**
       * Retrieves recently updated corporate bodies via the feed endpoint.
       * **EP API Endpoint:** `GET /corporate-bodies/feed`
       *
       * Fixed-window feed — no `timeframe` parameter per OpenAPI spec.
       * Extended timeout applied (120 s minimum).
       */
      async getCorporateBodiesFeed(): Promise<JSONLDResponse> {
        return this.get<JSONLDResponse>('corporate-bodies/feed', {
          format: 'application/ld+json',
        }, DEFAULT_TIMEOUTS.EP_FEED_SLOW_REQUEST_MS);
      }

      /**
       * Returns the list of all current EP Corporate Bodies for today's date.
       * **EP API Endpoint:** `GET /corporate-bodies/show-current`
       */
      async getCurrentCorporateBodies(params: {
        limit?: number;
        offset?: number;
      } = {}): Promise<PaginatedResponse<Committee>> {
        const limit = params.limit ?? 50;
        const offset = params.offset ?? 0;

        const response = await this.get<JSONLDResponse>(
          'corporate-bodies/show-current',
          { format: 'application/ld+json', offset, limit }
        );

        const items = Array.isArray(response.data) ? response.data : [];
        const bodies = items.map((item) => this.transformCorporateBody(item));
        const hasMore = bodies.length === limit;
        return { data: bodies, total: bodies.length + offset + (hasMore ? 1 : 0), limit, offset, hasMore };
      }
    }