Skip to content

Commit ec3ed37

Browse files
author
rzp-Piyush
committed
test: add proper unit tests for dataValidate.ts
- Export functions for testability - Add dummy proto fixture for isolated testing - Test fileExists, getDataFiles, getPackagesToValidate, validateWithProto - Test with both valid and invalid data
1 parent 1cf2a3b commit ec3ed37

File tree

6 files changed

+246
-212
lines changed

6 files changed

+246
-212
lines changed

.github/workflows/auto-generate-go-packages.yml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ jobs:
111111
matrix:
112112
package: ${{ fromJson(needs.check-dispatch.outputs.packages || needs.detect-changes.outputs.packages || '[]') }}
113113
fail-fast: false
114-
114+
115115
env:
116116
BRANCH: ${{ needs.check-dispatch.outputs.branch || needs.detect-changes.outputs.branch }}
117117
PR_NUMBER: ${{ needs.detect-changes.outputs.pr_number || '' }}
@@ -160,9 +160,9 @@ jobs:
160160
run: |
161161
git config --local user.email "github-actions[bot]@users.noreply.github.com"
162162
git config --local user.name "github-actions[bot]"
163-
163+
164164
git add "i18nify-data/go/${{ matrix.package }}"
165-
165+
166166
if git diff --cached --quiet; then
167167
echo "No changes to commit"
168168
else
@@ -180,20 +180,20 @@ jobs:
180180
TIMESTAMP=$(TZ=UTC git show -s --format=%cd --date=format-local:%Y%m%d%H%M%S "$COMMIT_SHA")
181181
VERSION="v0.0.0-${TIMESTAMP}-${SHORT_SHA}"
182182
echo "version=$VERSION" >> $GITHUB_OUTPUT
183-
183+
184184
chmod +x .github/scripts/go/update-dependencies.sh
185185
.github/scripts/go/update-dependencies.sh "${{ matrix.package }}" "$VERSION"
186186
187187
- name: Commit and push go.mod
188188
if: steps.check-proto.outputs.has_proto == 'true'
189189
run: |
190190
git add packages/i18nify-go/go.mod packages/i18nify-go/go.sum
191-
191+
192192
if git diff --cached --quiet; then
193193
echo "No go.mod changes"
194194
exit 0
195195
fi
196-
196+
197197
git commit -m "chore(go): update ${{ matrix.package }} to ${{ steps.version.outputs.version }}"
198198
git pull --rebase origin "$BRANCH" || true
199199
git push origin HEAD:"$BRANCH"
@@ -205,7 +205,7 @@ jobs:
205205
run: |
206206
VERSION="${{ steps.version.outputs.version }}"
207207
MODULE="github.com/razorpay/i18nify/i18nify-data/go/${{ matrix.package }}"
208-
208+
209209
gh pr comment "$PR_NUMBER" --body "## 📦 Package Updated
210210
211211
**Package:** \`${{ matrix.package }}\`
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "Test Item",
3+
"count": "not a number",
4+
"items": "should be array"
5+
}
6+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
syntax = "proto3";
2+
3+
package test_package;
4+
5+
message TestData {
6+
string name = 1;
7+
int32 count = 2;
8+
repeated string items = 3;
9+
}
10+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "Test Item",
3+
"count": 42,
4+
"items": ["item1", "item2", "item3"]
5+
}
6+

scripts/dataValidate.test.ts

Lines changed: 140 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,159 @@
1-
import * as fs from 'fs';
21
import * as path from 'path';
3-
import * as protobuf from 'protobufjs';
2+
import {
3+
fileExists,
4+
getDataFiles,
5+
validateWithProto,
6+
getPackagesToValidate,
7+
PACKAGE_CONFIGS,
8+
} from './dataValidate';
49

5-
// Test utilities
610
const TEST_DIR = path.join(__dirname, '../i18nify-data');
11+
const FIXTURES_DIR = path.join(__dirname, '__fixtures__');
712

813
describe('dataValidate', () => {
9-
describe('Proto files exist for all packages', () => {
10-
const packages = [
11-
'bankcodes',
12-
'currency',
13-
'country/metadata',
14-
'country/subdivisions',
15-
'phone-number/country-code-to-phone-number',
16-
'phone-number/dial-code-to-country',
17-
];
18-
19-
test.each(packages)('%s has a proto file', (pkg) => {
20-
const protoDir = path.join(TEST_DIR, pkg, 'proto');
21-
expect(fs.existsSync(protoDir)).toBe(true);
22-
23-
const protoFiles = fs.readdirSync(protoDir).filter(f => f.endsWith('.proto'));
24-
expect(protoFiles.length).toBeGreaterThan(0);
14+
describe('fileExists', () => {
15+
test('returns true for existing file', () => {
16+
expect(fileExists(path.join(TEST_DIR, 'currency/data.json'))).toBe(true);
17+
});
18+
19+
test('returns false for non-existing file', () => {
20+
expect(fileExists(path.join(TEST_DIR, 'nonexistent.json'))).toBe(false);
2521
});
2622
});
2723

28-
describe('Proto files are valid', () => {
29-
const protoConfigs = [
30-
{ pkg: 'bankcodes', proto: 'bankcodes.proto', message: 'BankCodes' },
31-
{ pkg: 'currency', proto: 'currency.proto', message: 'CurrencyData' },
32-
{ pkg: 'country/metadata', proto: 'country_metadata.proto', message: 'CountryMetadataData' },
33-
{ pkg: 'country/subdivisions', proto: 'country_subdivisions.proto', message: 'CountrySubdivisions' },
34-
{ pkg: 'phone-number/country-code-to-phone-number', proto: 'country_phone_info.proto', message: 'CountryPhoneData' },
35-
{ pkg: 'phone-number/dial-code-to-country', proto: 'dial_code_to_country.proto', message: 'DialCodeToCountryData' },
36-
];
37-
38-
test.each(protoConfigs)('$pkg proto loads and has $message', async ({ pkg, proto, message }) => {
39-
const protoPath = path.join(TEST_DIR, pkg, 'proto', proto);
40-
const root = await protobuf.load(protoPath);
41-
const MessageType = root.lookupType(message);
42-
expect(MessageType).toBeDefined();
24+
describe('getDataFiles', () => {
25+
test('returns single data.json for single pattern', () => {
26+
const files = getDataFiles('currency', 'single', TEST_DIR);
27+
expect(files).toHaveLength(1);
28+
expect(files[0]).toContain('data.json');
29+
});
30+
31+
test('returns multiple json files for multiple pattern', () => {
32+
const files = getDataFiles('country/subdivisions', 'multiple', TEST_DIR);
33+
expect(files.length).toBeGreaterThan(1);
34+
expect(files.some((f) => f.includes('IN.json'))).toBe(true);
35+
});
36+
37+
test('returns empty array for non-existing directory', () => {
38+
const files = getDataFiles('nonexistent', 'single', TEST_DIR);
39+
expect(files).toHaveLength(0);
4340
});
4441
});
4542

46-
describe('Data files validate against proto', () => {
47-
test('currency/data.json validates', async () => {
48-
const protoPath = path.join(TEST_DIR, 'currency/proto/currency.proto');
49-
const dataPath = path.join(TEST_DIR, 'currency/data.json');
50-
51-
const root = await protobuf.load(protoPath);
52-
const MessageType = root.lookupType('CurrencyData');
53-
const data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
54-
55-
const errMsg = MessageType.verify(data);
56-
expect(errMsg).toBeNull();
57-
});
58-
59-
test('country/metadata/data.json validates', async () => {
60-
const protoPath = path.join(TEST_DIR, 'country/metadata/proto/country_metadata.proto');
61-
const dataPath = path.join(TEST_DIR, 'country/metadata/data.json');
62-
63-
const root = await protobuf.load(protoPath);
64-
const MessageType = root.lookupType('CountryMetadataData');
65-
const data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
66-
67-
const errMsg = MessageType.verify(data);
68-
expect(errMsg).toBeNull();
43+
describe('getPackagesToValidate', () => {
44+
test('identifies correct packages from changed files', () => {
45+
const changedFiles = [
46+
'i18nify-data/currency/data.json',
47+
'i18nify-data/bankcodes/IN.json',
48+
];
49+
const packages = getPackagesToValidate(changedFiles);
50+
expect(packages.has('currency')).toBe(true);
51+
expect(packages.has('bankcodes')).toBe(true);
52+
expect(packages.size).toBe(2);
6953
});
7054

71-
test('country/subdivisions/IN.json validates', async () => {
72-
const protoPath = path.join(TEST_DIR, 'country/subdivisions/proto/country_subdivisions.proto');
73-
const dataPath = path.join(TEST_DIR, 'country/subdivisions/IN.json');
74-
75-
const root = await protobuf.load(protoPath);
76-
const MessageType = root.lookupType('CountrySubdivisions');
77-
const data = JSON.parse(fs.readFileSync(dataPath, 'utf-8'));
78-
79-
const errMsg = MessageType.verify(data);
80-
expect(errMsg).toBeNull();
81-
});
82-
83-
test('phone-number data files validate', async () => {
84-
// country-code-to-phone-number
85-
const proto1 = path.join(TEST_DIR, 'phone-number/country-code-to-phone-number/proto/country_phone_info.proto');
86-
const data1 = path.join(TEST_DIR, 'phone-number/country-code-to-phone-number/data.json');
87-
88-
const root1 = await protobuf.load(proto1);
89-
const Type1 = root1.lookupType('CountryPhoneData');
90-
const json1 = JSON.parse(fs.readFileSync(data1, 'utf-8'));
91-
expect(Type1.verify(json1)).toBeNull();
92-
93-
// dial-code-to-country
94-
const proto2 = path.join(TEST_DIR, 'phone-number/dial-code-to-country/proto/dial_code_to_country.proto');
95-
const data2 = path.join(TEST_DIR, 'phone-number/dial-code-to-country/data.json');
96-
97-
const root2 = await protobuf.load(proto2);
98-
const Type2 = root2.lookupType('DialCodeToCountryData');
99-
const json2 = JSON.parse(fs.readFileSync(data2, 'utf-8'));
100-
expect(Type2.verify(json2)).toBeNull();
55+
test('returns empty set for unrelated files', () => {
56+
const changedFiles = ['packages/i18nify-go/main.go', 'README.md'];
57+
const packages = getPackagesToValidate(changedFiles);
58+
expect(packages.size).toBe(0);
59+
});
60+
61+
test('deduplicates packages', () => {
62+
const changedFiles = [
63+
'i18nify-data/currency/data.json',
64+
'i18nify-data/currency/proto/currency.proto',
65+
];
66+
const packages = getPackagesToValidate(changedFiles);
67+
expect(packages.size).toBe(1);
68+
expect(packages.has('currency')).toBe(true);
69+
});
70+
});
71+
72+
describe('validateWithProto', () => {
73+
const fixtureProto = path.join(
74+
FIXTURES_DIR,
75+
'test-package/proto/test.proto',
76+
);
77+
const validData = path.join(FIXTURES_DIR, 'test-package/valid-data.json');
78+
const invalidData = path.join(
79+
FIXTURES_DIR,
80+
'test-package/invalid-data.json',
81+
);
82+
83+
test('validates correct data successfully', async () => {
84+
const result = await validateWithProto(
85+
fixtureProto,
86+
'TestData',
87+
validData,
88+
);
89+
expect(result.valid).toBe(true);
90+
expect(result.error).toBeUndefined();
91+
});
92+
93+
test('fails validation for invalid data', async () => {
94+
const result = await validateWithProto(
95+
fixtureProto,
96+
'TestData',
97+
invalidData,
98+
);
99+
expect(result.valid).toBe(false);
100+
expect(result.error).toBeDefined();
101+
});
102+
103+
test('fails for non-existing proto file', async () => {
104+
const result = await validateWithProto(
105+
'/nonexistent.proto',
106+
'TestData',
107+
validData,
108+
);
109+
expect(result.valid).toBe(false);
110+
expect(result.error).toBeDefined();
111+
});
112+
113+
test('fails for non-existing data file', async () => {
114+
const result = await validateWithProto(
115+
fixtureProto,
116+
'TestData',
117+
'/nonexistent.json',
118+
);
119+
expect(result.valid).toBe(false);
120+
expect(result.error).toBeDefined();
101121
});
102122
});
103123

104-
describe('Invalid data fails validation', () => {
105-
test('invalid country subdivision data fails', async () => {
106-
const protoPath = path.join(TEST_DIR, 'country/subdivisions/proto/country_subdivisions.proto');
107-
108-
const root = await protobuf.load(protoPath);
109-
const MessageType = root.lookupType('CountrySubdivisions');
110-
111-
// Invalid data - states should be object, not string
112-
const invalidData = {
113-
country_name: 'Test',
114-
states: {
115-
'INVALID': 'this should be an object, not a string'
116-
}
117-
};
118-
119-
const errMsg = MessageType.verify(invalidData);
120-
expect(errMsg).not.toBeNull();
121-
});
122-
123-
test('wrong nested type fails validation', async () => {
124-
const protoPath = path.join(TEST_DIR, 'country/subdivisions/proto/country_subdivisions.proto');
125-
126-
const root = await protobuf.load(protoPath);
127-
const MessageType = root.lookupType('CountrySubdivisions');
128-
129-
// Wrong type - cities should be object with City structure, not array
130-
const wrongTypeData = {
131-
country_name: 'Test',
132-
states: {
133-
'TEST': {
134-
name: 'Test State',
135-
cities: ['city1', 'city2'] // Should be map<string, City>, not array
136-
}
137-
}
138-
};
139-
140-
const errMsg = MessageType.verify(wrongTypeData);
141-
expect(errMsg).not.toBeNull();
124+
describe('PACKAGE_CONFIGS', () => {
125+
test('all configs have required fields', () => {
126+
for (const config of Object.values(PACKAGE_CONFIGS)) {
127+
expect(config.protoPath).toBeDefined();
128+
expect(config.dataPattern).toMatch(/^(single|multiple)$/);
129+
expect(config.rootMessageName).toBeDefined();
130+
}
131+
});
132+
133+
test('all proto files exist', () => {
134+
for (const config of Object.values(PACKAGE_CONFIGS)) {
135+
expect(fileExists(config.protoPath)).toBe(true);
136+
}
142137
});
143138
});
144-
});
145139

140+
describe('Integration: Real data validation', () => {
141+
test('currency/data.json validates', async () => {
142+
const result = await validateWithProto(
143+
PACKAGE_CONFIGS['currency'].protoPath,
144+
PACKAGE_CONFIGS['currency'].rootMessageName,
145+
path.join(TEST_DIR, 'currency/data.json'),
146+
);
147+
expect(result.valid).toBe(true);
148+
});
149+
150+
test('country/subdivisions/IN.json validates', async () => {
151+
const result = await validateWithProto(
152+
PACKAGE_CONFIGS['country/subdivisions'].protoPath,
153+
PACKAGE_CONFIGS['country/subdivisions'].rootMessageName,
154+
path.join(TEST_DIR, 'country/subdivisions/IN.json'),
155+
);
156+
expect(result.valid).toBe(true);
157+
});
158+
});
159+
});

0 commit comments

Comments
 (0)