diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..60542f0 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +--- +name: Unit Tests + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + workflow_dispatch: true + +jobs: + test: + name: Test on Node.js ${{ matrix.node-version }} + runs-on: ubuntu-latest + + permissions: + contents: read + + strategy: + matrix: + node-version: [18, 20, 22, latest] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Upload test output as artifact + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-output-node-${{ matrix.node-version }} + path: test-output.json + if-no-files-found: ignore + retention-days: 7 diff --git a/test-benchmark.json b/test-benchmark.json new file mode 100644 index 0000000..8cb41db --- /dev/null +++ b/test-benchmark.json @@ -0,0 +1,40 @@ +{ + "metadata": { + "generated": "2024-01-01T00:00:00.000Z", + "source": "https://www.itu.int/pub/T-SP-E.212B-202401-I/en", + "etag": "\"benchmark\"" + }, + "areas": { + "united states": [ + { + "name": "Verizon Wireless", + "mcc": "310", + "mnc": "004" + }, + { + "name": "AT&T Mobility", + "mcc": "310", + "mnc": "410" + } + ], + "canada": [ + { + "name": "Rogers Wireless", + "mcc": "302", + "mnc": "720" + } + ], + "united kingdom": [ + { + "name": "Vodafone", + "mcc": "234", + "mnc": "015" + } + ] + }, + "areaNames": [ + "United States", + "Canada", + "United Kingdom" + ] +} diff --git a/test.mjs b/test.mjs index eafc5b3..d30add4 100644 --- a/test.mjs +++ b/test.mjs @@ -3,18 +3,19 @@ import assert from 'node:assert/strict'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; +import { spawnSync } from 'child_process'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const testOutputPath = path.join(__dirname, 'test-output.json'); +const benchmarkPath = path.join(__dirname, 'test-benchmark.json'); -test('MCC/MNC tool', async (t) => { +test('MCC/MNC tool - structure validation', async (t) => { // Cleanup from previous runs try { await fs.unlink(testOutputPath); } catch {} // Run script - const { spawnSync } = await import('child_process'); const result = spawnSync('node', ['index.mjs', '--output', testOutputPath], { stdio: 'inherit' }); @@ -32,6 +33,65 @@ test('MCC/MNC tool', async (t) => { assert.ok(Array.isArray(data.areaNames), 'Should have areaNames array'); assert.ok(Object.keys(data.areas).length > 0, 'Should have at least one area'); + // Cleanup + await fs.unlink(testOutputPath); +}); + +test('MCC/MNC tool - content validation', async (t) => { + // Cleanup from previous runs + try { + await fs.unlink(testOutputPath); + } catch {} + + // Run script + const result = spawnSync('node', ['index.mjs', '--output', testOutputPath], { + stdio: 'inherit' + }); + + assert.strictEqual(result.status, 0, 'Script should exit with code 0'); + + // Load actual and benchmark data + const actualContent = await fs.readFile(testOutputPath, 'utf-8'); + const actual = JSON.parse(actualContent); + + const benchmarkContent = await fs.readFile(benchmarkPath, 'utf-8'); + const benchmark = JSON.parse(benchmarkContent); + + // Validate structure matches benchmark + assert.ok(actual.metadata, 'Should have metadata object'); + assert.ok(actual.areas, 'Should have areas object'); + assert.ok(actual.areaNames, 'Should have areaNames array'); + + // Validate all area names are strings + assert.ok(actual.areaNames.every(name => typeof name === 'string'), 'All area names should be strings'); + + // Validate areas structure + for (const [areaKey, entries] of Object.entries(actual.areas)) { + assert.ok(Array.isArray(entries), `Area "${areaKey}" should be an array`); + for (const entry of entries) { + assert.strictEqual(typeof entry.name, 'string', `Entry name should be string in area "${areaKey}"`); + assert.strictEqual(typeof entry.mcc, 'string', `Entry mcc should be string in area "${areaKey}"`); + assert.strictEqual(typeof entry.mnc, 'string', `Entry mnc should be string in area "${areaKey}"`); + assert.ok(entry.name.length > 0, `Entry name should not be empty in area "${areaKey}"`); + assert.ok(entry.mcc.length > 0, `Entry mcc should not be empty in area "${areaKey}"`); + assert.ok(entry.mnc.length > 0, `Entry mnc should not be empty in area "${areaKey}"`); + } + } + + // Validate that we have a reasonable number of areas (at least as many as benchmark) + assert.ok( + Object.keys(actual.areas).length >= Object.keys(benchmark.areas).length, + `Should have at least ${Object.keys(benchmark.areas).length} areas` + ); + + // Validate that we have a reasonable number of entries + const actualEntriesCount = Object.values(actual.areas).flat().length; + const benchmarkEntriesCount = Object.values(benchmark.areas).flat().length; + assert.ok( + actualEntriesCount >= benchmarkEntriesCount, + `Should have at least ${benchmarkEntriesCount} entries, got ${actualEntriesCount}` + ); + // Cleanup await fs.unlink(testOutputPath); }); \ No newline at end of file