Skip to content

Commit 58965c2

Browse files
Merge pull request #22 from duckduckgo/jmatthews/pixel-owner-validation
Check that pixel owners are Github userids in a list of approved ids as part of pixel validation
2 parents b560219 + 6a6bbd2 commit 58965c2

File tree

5 files changed

+82
-9
lines changed

5 files changed

+82
-9
lines changed

bin/validate_schema.mjs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import fs from 'fs';
44
import JSON5 from 'json5';
55
import path from 'path';
66
import yargs from 'yargs';
7+
import yaml from 'js-yaml';
78

89
import { DefinitionsValidator } from '../src/definitions_validator.mjs';
910
import { logErrors } from '../src/error_utils.mjs';
@@ -25,6 +26,12 @@ const argv = yargs(hideBin(process.argv))
2526
},
2627
});
2728
})
29+
.option('githubUserMap', {
30+
alias: 'g',
31+
describe: 'Path to the GitHub user map YAML file',
32+
type: 'string',
33+
demandOption: false,
34+
})
2835
.option('file', {
2936
alias: 'f',
3037
type: 'string',
@@ -51,25 +58,38 @@ const experiments = fileUtils.readExperimentsDef(mainDir);
5158
logErrors('ERROR in native_experiments.json:', validator.validateExperimentsDefinition(experiments));
5259

5360
// 3) Validate pixels and params
54-
function validateFile(file) {
61+
function validateFile(file, userMap) {
5562
console.log(`Validating pixels definition: ${file}`);
5663
const pixelsDef = JSON5.parse(fs.readFileSync(file));
57-
logErrors(`ERROR in ${file}:`, validator.validatePixelsDefinition(pixelsDef));
64+
logErrors(`ERROR in ${file}:`, validator.validatePixelsDefinition(pixelsDef, userMap));
5865
}
5966

60-
function validateFolder(folder) {
67+
function validateFolder(folder, userMap) {
6168
fs.readdirSync(folder, { recursive: true }).forEach((file) => {
6269
const fullPath = path.join(folder, file);
6370
if (fs.statSync(fullPath).isDirectory()) {
6471
return;
6572
}
6673

67-
validateFile(fullPath);
74+
validateFile(fullPath, userMap);
6875
});
6976
}
77+
let userMap = null;
78+
79+
if (argv.githubUserMap) {
80+
console.log(`Reading GitHub user map from: ${argv.githubUserMap}`);
81+
try {
82+
userMap = yaml.load(fs.readFileSync(argv.githubUserMap, 'utf8'));
83+
} catch (error) {
84+
console.error(`Error reading GitHub user map from ${argv.githubUserMap}:`, error.message);
85+
process.exit(1);
86+
}
87+
} else {
88+
console.log('No GitHub user map provided, skipping owner validation.');
89+
}
7090

7191
if (argv.file) {
72-
validateFile(path.join(pixelsDir, argv.file));
92+
validateFile(path.join(pixelsDir, argv.file), userMap);
7393
} else {
74-
validateFolder(pixelsDir);
94+
validateFolder(pixelsDir, userMap);
7595
}

src/definitions_validator.mjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class DefinitionsValidator {
8484
* @param {*} pixelsDef - object containing multiple pixel definitions
8585
* @returns {Array<string>} - array of error messages
8686
*/
87-
validatePixelsDefinition(pixelsDef) {
87+
validatePixelsDefinition(pixelsDef, userMap) {
8888
// 1) Validate that pixel definition conforms to schema
8989
if (!this.#ajvValidatePixels(pixelsDef)) {
9090
// Doesn't make sense to check the rest if main definition is invalid
@@ -94,13 +94,23 @@ export class DefinitionsValidator {
9494
// 2) Validate that:
9595
// (a) there are no duplicate prefixes and
9696
// (b) shortcuts, params, and suffixes can be compiled into a separate schema
97+
// (c) all owners are valid github usernames in the provided userMap
9798
const errors = [];
9899
Object.entries(pixelsDef).forEach(([pixelName, pixelDef]) => {
99100
if (this.#definedPrefixes.has(pixelName)) {
100101
errors.push(`${pixelName} --> Conflicting/duplicated definitions found!`);
101102
return;
102103
}
103104

105+
// If a github user map is provided, check if all owners are valid
106+
if (userMap) {
107+
for (const owner of pixelDef.owners) {
108+
if (!userMap[owner]) {
109+
errors.push(`Owner ${owner} for pixel ${pixelName} not in list of acceptable github user names`);
110+
}
111+
}
112+
}
113+
104114
this.#definedPrefixes.add(pixelName);
105115
try {
106116
this.#paramsValidator.compileSuffixesSchema(pixelDef.suffixes);

tests/integration_test.mjs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ const validDefsPath = path.join('tests', 'test_data', 'valid');
1111
const liveValidationResultsPath = path.join(validDefsPath, 'expected_processing_results');
1212
const validCaseInsensitiveDefsPath = path.join('tests', 'test_data', 'valid_case_insensitive');
1313
const invalidDefsPath = path.join('tests', 'test_data', 'invalid');
14-
describe('Invalid defs', () => {
14+
const validUserMapPath = path.join('tests', 'test_data', 'valid', 'user_map.yml');
15+
16+
describe('Invalid defs without user map', () => {
1517
it('should output all required params', (done) => {
1618
exec(`npm run validate-ddg-pixel-defs ${invalidDefsPath}`, (error, _, stderr) => {
1719
const pixelPath = path.join(invalidDefsPath, 'pixels', 'pixels.json');
@@ -34,7 +36,27 @@ describe('Invalid defs', () => {
3436
}).timeout(timeout);
3537
});
3638

37-
describe('Valid defs', () => {
39+
describe('Invalid owner with user map', () => {
40+
it('should output error for invalid owner', (done) => {
41+
// Careful: We need the -- to pass the -g flag to the script
42+
exec(`npm run validate-ddg-pixel-defs -- ${invalidDefsPath} -g ${validUserMapPath}`, (error, _, stderr) => {
43+
const pixelPath = path.join(invalidDefsPath, 'pixels', 'invalid_owner.json');
44+
45+
// All of these should be present in the output
46+
const expectedErrors = [
47+
`ERROR in ${pixelPath}: Owner username_not_in_user_map for pixel pixel_with_invalid_owner not in list of acceptable github user names`,
48+
];
49+
50+
const errors = stderr.trim().split('\n');
51+
expect(errors).to.include.members(expectedErrors);
52+
expect(error.code).to.equal(1);
53+
54+
done();
55+
});
56+
}).timeout(timeout);
57+
});
58+
59+
describe('Valid defs without user map', () => {
3860
it('should exit normally', (done) => {
3961
exec(`npm run validate-ddg-pixel-defs ${validDefsPath}`, (error, _, stderr) => {
4062
expect(stderr.length).to.equal(0);
@@ -45,6 +67,17 @@ describe('Valid defs', () => {
4567
}).timeout(timeout);
4668
});
4769

70+
describe('Valid defs with user map', () => {
71+
it('should exit normally', (done) => {
72+
exec(`npm run validate-ddg-pixel-defs -- ${validDefsPath} -g ${validUserMapPath}`, (error, _, stderr) => {
73+
expect(stderr.length).to.equal(0);
74+
expect(error).to.equal(null);
75+
76+
done();
77+
});
78+
}).timeout(timeout);
79+
});
80+
4881
describe('Validate live pixels', () => {
4982
it('case sensitive - should produce expected errors', (done) => {
5083
exec(`npm run preprocess-defs ${validDefsPath}`, (error, _, stderr) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"pixel_with_invalid_owner": {
3+
"description": "Test pixel with invalid owner.",
4+
"owners": ["username_not_in_user_map"],
5+
"triggers": ["other"]
6+
}
7+
}

tests/test_data/valid/user_map.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Format is GITHUB_USERNAME: ASANA_USER_ID
2+
ddg_username: '1234567890'
3+
tester: '1234567891'

0 commit comments

Comments
 (0)