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 });