/ tests / utils / flag-parser.test.js
flag-parser.test.js
  1  /**
  2   * Flag Parser Tests
  3   *
  4   * Tests for parseSkipFlags(), parseLimitFlag(), parseForceFlag(),
  5   * parseCountryFlag(), parseTypeFlag(), parseCsvFlag(), parseThresholdFlag(),
  6   * and parseFlags().
  7   * Pure functions — no external dependencies.
  8   */
  9  
 10  import { test, describe } from 'node:test';
 11  import assert from 'node:assert/strict';
 12  
 13  import {
 14    parseSkipFlags,
 15    parseLimitFlag,
 16    parseForceFlag,
 17    parseCountryFlag,
 18    parseTypeFlag,
 19    parseCsvFlag,
 20    parseThresholdFlag,
 21    parseFlags,
 22  } from '../../src/utils/flag-parser.js';
 23  
 24  // ─── parseSkipFlags ────────────────────────────────────────────────────────────
 25  
 26  describe('parseSkipFlags', () => {
 27    test('returns empty Set when --skip not present', () => {
 28      const result = parseSkipFlags(['node', 'script.js']);
 29      assert.ok(result instanceof Set);
 30      assert.equal(result.size, 0);
 31    });
 32  
 33    test('returns empty Set when --skip is last arg (no value)', () => {
 34      const result = parseSkipFlags(['node', 'script.js', '--skip']);
 35      assert.equal(result.size, 0);
 36    });
 37  
 38    test('returns single stage', () => {
 39      const result = parseSkipFlags(['node', 'script.js', '--skip', 'serps']);
 40      assert.ok(result.has('serps'));
 41      assert.equal(result.size, 1);
 42    });
 43  
 44    test('splits comma-separated stages', () => {
 45      const result = parseSkipFlags(['node', 'script.js', '--skip', 'serps,assets,scoring']);
 46      assert.ok(result.has('serps'));
 47      assert.ok(result.has('assets'));
 48      assert.ok(result.has('scoring'));
 49      assert.equal(result.size, 3);
 50    });
 51  
 52    test('trims whitespace from stage names', () => {
 53      const result = parseSkipFlags(['node', 'script.js', '--skip', ' serps , assets ']);
 54      assert.ok(result.has('serps'));
 55      assert.ok(result.has('assets'));
 56    });
 57  
 58    test('filters empty entries from split', () => {
 59      const result = parseSkipFlags(['node', 'script.js', '--skip', 'serps,,assets']);
 60      assert.ok(!result.has(''));
 61      assert.equal(result.size, 2);
 62    });
 63  });
 64  
 65  // ─── parseLimitFlag ────────────────────────────────────────────────────────────
 66  
 67  describe('parseLimitFlag', () => {
 68    test('returns null when --limit not present', () => {
 69      assert.equal(parseLimitFlag(['node', 'script.js']), null);
 70    });
 71  
 72    test('returns null when --limit is last arg', () => {
 73      assert.equal(parseLimitFlag(['node', 'script.js', '--limit']), null);
 74    });
 75  
 76    test('parses integer limit', () => {
 77      assert.equal(parseLimitFlag(['node', 'script.js', '--limit', '10']), 10);
 78    });
 79  
 80    test('parses limit of 1', () => {
 81      assert.equal(parseLimitFlag(['node', 'script.js', '--limit', '1']), 1);
 82    });
 83  
 84    test('returns null for non-numeric value', () => {
 85      assert.equal(parseLimitFlag(['node', 'script.js', '--limit', 'abc']), null);
 86    });
 87  
 88    test('works with other flags present', () => {
 89      const result = parseLimitFlag([
 90        'node',
 91        'script.js',
 92        '--force',
 93        '--limit',
 94        '50',
 95        '--skip',
 96        'serps',
 97      ]);
 98      assert.equal(result, 50);
 99    });
100  });
101  
102  // ─── parseForceFlag ────────────────────────────────────────────────────────────
103  
104  describe('parseForceFlag', () => {
105    test('returns false when --force not present', () => {
106      assert.equal(parseForceFlag(['node', 'script.js']), false);
107    });
108  
109    test('returns true when --force present', () => {
110      assert.equal(parseForceFlag(['node', 'script.js', '--force']), true);
111    });
112  
113    test('works with other flags', () => {
114      assert.equal(parseForceFlag(['node', 'script.js', '--limit', '10', '--force']), true);
115    });
116  });
117  
118  // ─── parseCountryFlag ─────────────────────────────────────────────────────────
119  
120  describe('parseCountryFlag', () => {
121    test('returns null when --country not present', () => {
122      assert.equal(parseCountryFlag(['node', 'script.js']), null);
123    });
124  
125    test('returns null when --country is last arg', () => {
126      assert.equal(parseCountryFlag(['node', 'script.js', '--country']), null);
127    });
128  
129    test('returns country code', () => {
130      assert.equal(parseCountryFlag(['node', 'script.js', '--country', 'UK']), 'UK');
131    });
132  
133    test('returns country code as-is (no uppercasing)', () => {
134      assert.equal(parseCountryFlag(['node', 'script.js', '--country', 'au']), 'au');
135    });
136  });
137  
138  // ─── parseTypeFlag ─────────────────────────────────────────────────────────────
139  
140  describe('parseTypeFlag', () => {
141    test('returns null when --type not present', () => {
142      assert.equal(parseTypeFlag(['node', 'script.js']), null);
143    });
144  
145    test('returns null when --type is last arg', () => {
146      assert.equal(parseTypeFlag(['node', 'script.js', '--type']), null);
147    });
148  
149    test('returns type value', () => {
150      assert.equal(parseTypeFlag(['node', 'script.js', '--type', 'businesses']), 'businesses');
151    });
152  
153    test('returns regions type', () => {
154      assert.equal(parseTypeFlag(['node', 'script.js', '--type', 'regions']), 'regions');
155    });
156  });
157  
158  // ─── parseCsvFlag ──────────────────────────────────────────────────────────────
159  
160  describe('parseCsvFlag', () => {
161    test('returns null when --csv not present', () => {
162      assert.equal(parseCsvFlag(['node', 'script.js']), null);
163    });
164  
165    test('returns null when --csv is last arg', () => {
166      assert.equal(parseCsvFlag(['node', 'script.js', '--csv']), null);
167    });
168  
169    test('returns CSV file path', () => {
170      const path = 'data/au/businesses-search-volume.csv';
171      assert.equal(parseCsvFlag(['node', 'script.js', '--csv', path]), path);
172    });
173  });
174  
175  // ─── parseThresholdFlag ────────────────────────────────────────────────────────
176  
177  describe('parseThresholdFlag', () => {
178    test('returns null when --threshold not present', () => {
179      assert.equal(parseThresholdFlag(['node', 'script.js']), null);
180    });
181  
182    test('returns null when --threshold is last arg', () => {
183      assert.equal(parseThresholdFlag(['node', 'script.js', '--threshold']), null);
184    });
185  
186    test('parses integer threshold', () => {
187      assert.equal(parseThresholdFlag(['node', 'script.js', '--threshold', '200000']), 200000);
188    });
189  
190    test('returns null for non-numeric value', () => {
191      assert.equal(parseThresholdFlag(['node', 'script.js', '--threshold', 'high']), null);
192    });
193  });
194  
195  // ─── parseFlags (combined) ────────────────────────────────────────────────────
196  
197  describe('parseFlags', () => {
198    test('returns all default values when no flags present', () => {
199      const result = parseFlags(['node', 'script.js']);
200      assert.ok(result.skip instanceof Set);
201      assert.equal(result.skip.size, 0);
202      assert.equal(result.limit, null);
203      assert.equal(result.force, false);
204      assert.equal(result.country, null);
205      assert.equal(result.type, null);
206      assert.equal(result.csv, null);
207      assert.equal(result.threshold, null);
208    });
209  
210    test('parses all flags together', () => {
211      const args = [
212        'node',
213        'script.js',
214        '--skip',
215        'serps,assets',
216        '--limit',
217        '10',
218        '--force',
219        '--country',
220        'UK',
221        '--type',
222        'businesses',
223        '--threshold',
224        '200000',
225      ];
226      const result = parseFlags(args);
227      assert.ok(result.skip.has('serps'));
228      assert.ok(result.skip.has('assets'));
229      assert.equal(result.limit, 10);
230      assert.equal(result.force, true);
231      assert.equal(result.country, 'UK');
232      assert.equal(result.type, 'businesses');
233      assert.equal(result.threshold, 200000);
234    });
235  
236    test('returns correct shape', () => {
237      const result = parseFlags([]);
238      assert.ok('skip' in result);
239      assert.ok('limit' in result);
240      assert.ok('force' in result);
241      assert.ok('country' in result);
242      assert.ok('type' in result);
243      assert.ok('csv' in result);
244      assert.ok('threshold' in result);
245    });
246  });