All files / scripts/lib fetchGithub.js

97.56% Statements 40/41
93.05% Branches 67/72
100% Functions 2/2
100% Lines 32/32

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 673x     12x         11x 2x 2x                               12x 12x   12x 12x   12x   12x 12x 12x   3x     9x 9x 9x 8x 8x 1x   7x 7x 7x 7x 6x 1x   7x 6x   3x 1x   2x       3x  
const { getAxios } = require('./axiosLoader');
 
async function runAuthTest(axios, token, debug, timeout) {
  const testRes = await axios.post(
    'https://api.github.com/graphql',
    { query: '{ viewer { login } }' },
    { headers: { Authorization: token ? `Bearer ${token}` : undefined }, timeout: timeout || 8000 }
  );
  if (!testRes || !testRes.data || !testRes.data.data || !testRes.data.data.viewer) {
    if (debug) console.log('fetchGithub: auth test response', testRes && testRes.data);
    throw new Error('GraphQL auth test failed');
  }
}
 
/**
 * Runs a GitHub GraphQL query, preceded by a lightweight auth test to surface
 * token errors with a clear message before the real query fires.
 * Extracts repository nodes from either a pinnedItems or repositories response shape.
 *
 * @param {string} token - GitHub personal access token
 * @param {string} query - GraphQL query string
 * @param {object} [variables] - Query variables; only attached to the payload when the query uses '$' parameters
 * @param {object} [opts] - Optional overrides: { timeout }
 * @returns {Promise<object[]>} Array of repository nodes
 */
async function runGraphQL(token, query, variables = { login: 'keglev' }, opts = {}) {
  const axios = getAxios();
  Iif (!axios) throw new Error('fetchGithub: failed to require axios (is it installed?)');
 
  const DEBUG = process.env.DEBUG_FETCH === '1' || process.env.DEBUG_FETCH === 'true';
  const timeout = (opts && opts.timeout) || 10000;
  // Only include variables when the query declares parameters; bare queries reject extra variables
  const payload = (typeof query === 'string' && query.includes('$')) ? { query, variables } : { query };
 
  try {
    if (DEBUG) console.log('fetchGithub: running auth test');
    await runAuthTest(axios, token, DEBUG, timeout);
  } catch (e) {
    throw new Error('GraphQL auth/test query failed: ' + (e && e.message));
  }
 
  try {
    if (DEBUG) console.log('fetchGithub: sending query, variables:', variables);
    const res = await axios.post('https://api.github.com/graphql', payload, { headers: { Authorization: token ? `Bearer ${token}` : undefined }, timeout });
    if (DEBUG) try { console.log('fetchGithub: response status', res && res.status); } catch (err) {}
    if (res && res.data && res.data.errors && res.data.errors.length) {
      throw new Error('GraphQL errors: ' + JSON.stringify(res.data.errors));
    }
    const body = res && res.data ? res.data : null;
    const user = body && body.data && body.data.user;
    let nodes = null;
    if (user) {
      if (user.pinnedItems && Array.isArray(user.pinnedItems.nodes)) nodes = user.pinnedItems.nodes;
      else Eif (user.repositories && Array.isArray(user.repositories.nodes)) nodes = user.repositories.nodes;
    }
    if (!Array.isArray(nodes)) throw new Error('Invalid GraphQL response: ' + JSON.stringify(body));
    return nodes;
  } catch (err) {
    if (err && err.response && err.response.data) {
      throw new Error('GraphQL request failed: ' + JSON.stringify(err.response.data, null, 2));
    }
    throw new Error((err && err.message) || String(err));
  }
}
 
module.exports = { runGraphQL };