Updated product group parser to include tech leads and Security & compliance group. (#33849)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #33848 Verified by running the GitHub Action: ``` ***"timestamp":"2025-10-05T21:10:43.408Z","level":"info","message":"Parsing product groups from /home/runner/work/fleet/fleet/handbook/company/product-groups.md"*** ***"timestamp":"2025-10-05T21:10:43.409Z","level":"info","message":"Found 1 tech lead(s) in mdm group: JordanMontgomery"*** ***"timestamp":"2025-10-05T21:10:43.409Z","level":"info","message":"Found 3 developer(s) in mdm group: gillespi314, ghernandez345, MagnusHJensen"*** ***"timestamp":"2025-10-05T21:10:43.409Z","level":"info","message":"Found 1 tech lead(s) in orchestration group: lucasmrod"*** ***"timestamp":"2025-10-05T21:10:43.410Z","level":"info","message":"Found 4 developer(s) in orchestration group: sgress454, juan-fdz-hawa, iansltx, ksykulev"*** ***"timestamp":"2025-10-05T21:10:43.410Z","level":"info","message":"Found 1 tech lead(s) in software group: cdcme"*** ***"timestamp":"2025-10-05T21:10:43.410Z","level":"info","message":"Found 4 developer(s) in software group: rachelelysia, jahzielv, jkatz01, mna"*** ***"timestamp":"2025-10-05T21:10:43.410Z","level":"info","message":"Found 1 tech lead(s) in security-compliance group: getvictor"*** ***"timestamp":"2025-10-05T21:10:43.410Z","level":"info","message":"Found 3 developer(s) in security-compliance group: dantecatalfamo, jacobshandling, mostlikelee"*** ``` # Checklist for submitter This is not a product change. Only an update to gathering engineering metrics. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Adds support for the Security & compliance group. - Extracts and reports Tech Lead memberships, including inclusion in overall engineering stats. - Bug Fixes - More reliable username parsing (handles hyphens, numbers, multi-line cells). - Stricter validation halts on missing sections or rows to prevent partial/inaccurate results, with clearer error messages. - Tests - Expanded coverage for new groups, tech leads, error paths, and edge-case username formats. - Chores - Updated dependencies for stability and maintenance. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
+592
-617
File diff suppressed because it is too large
Load Diff
@@ -29,14 +29,14 @@
|
||||
"analytics"
|
||||
],
|
||||
"dependencies": {
|
||||
"@google-cloud/bigquery": "^8.1.0",
|
||||
"dotenv": "^17.0.0",
|
||||
"@google-cloud/bigquery": "^8.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"octokit": "^5.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.30.0",
|
||||
"eslint": "^9.30.0",
|
||||
"jest": "^30.0.3"
|
||||
"@eslint/js": "^9.37.0",
|
||||
"eslint": "^9.37.0",
|
||||
"jest": "^30.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20.18.1"
|
||||
|
||||
@@ -34,6 +34,7 @@ export const parseProductGroups = (filePath) => {
|
||||
* Extracts usernames from markdown content
|
||||
* @param {string} content - Markdown content
|
||||
* @returns {Array<{group: string, username: string}>} Array of user group mappings
|
||||
* @throws {Error} If required sections are missing or validation fails
|
||||
*/
|
||||
const extractUsernamesFromMarkdown = (content) => {
|
||||
const userGroups = [];
|
||||
@@ -43,6 +44,7 @@ const extractUsernamesFromMarkdown = (content) => {
|
||||
'MDM group': 'mdm',
|
||||
'Orchestration group': 'orchestration',
|
||||
'Software group': 'software',
|
||||
'Security & compliance group': 'security-compliance',
|
||||
};
|
||||
|
||||
// For each group, find its section and extract usernames
|
||||
@@ -56,10 +58,17 @@ const extractUsernamesFromMarkdown = (content) => {
|
||||
|
||||
if (sectionMatch) {
|
||||
const sectionContent = sectionMatch[1];
|
||||
const usernames = extractUsernamesFromSection(sectionContent, groupName);
|
||||
userGroups.push(...usernames);
|
||||
try {
|
||||
const usernames = extractUsernamesFromSection(sectionContent, groupName);
|
||||
userGroups.push(...usernames);
|
||||
} catch (err) {
|
||||
logger.error(`Error extracting usernames from ${sectionName}`, {}, err);
|
||||
throw err;
|
||||
}
|
||||
} else {
|
||||
logger.warn(`Section not found: ${sectionName}`);
|
||||
const error = new Error(`Section not found: ${sectionName}`);
|
||||
logger.error(error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +81,38 @@ const extractUsernamesFromMarkdown = (content) => {
|
||||
/**
|
||||
* Extracts usernames from a specific section
|
||||
* @param {string} sectionContent - Content of the section
|
||||
* @param {string} groupName - Name of the group (mdm, orchestration, software)
|
||||
* @param {string} groupName - Name of the group (mdm, orchestration, software, security-compliance)
|
||||
* @returns {Array<{group: string, username: string}>} Array of user group mappings
|
||||
* @throws {Error} If Tech Lead or Developer requirements are not met
|
||||
*/
|
||||
const extractUsernamesFromSection = (sectionContent, groupName) => {
|
||||
const userGroups = [];
|
||||
|
||||
// Look for the Tech Lead row in the table
|
||||
const techLeadRowMatch = sectionContent.match(
|
||||
/\|\s*Tech Lead\s*\|\s*([\s\S]*?)(?=\n\||\n\n|$)/
|
||||
);
|
||||
|
||||
if (!techLeadRowMatch) {
|
||||
throw new Error(`No Tech Lead row found in ${groupName} group section`);
|
||||
}
|
||||
|
||||
const techLeadCell = techLeadRowMatch[1];
|
||||
const techLeadUsernames = extractUsernamesFromCell(techLeadCell);
|
||||
|
||||
if (techLeadUsernames.length === 0) {
|
||||
throw new Error(`No Tech Lead found in ${groupName} group`);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Found ${techLeadUsernames.length} tech lead(s) in ${groupName} group: ${techLeadUsernames.join(', ')}`
|
||||
);
|
||||
|
||||
for (const username of techLeadUsernames) {
|
||||
userGroups.push({ group: groupName, username });
|
||||
userGroups.push({ group: 'engineering', username });
|
||||
}
|
||||
|
||||
// Look for the Developer row in the table
|
||||
// The pattern needs to handle multi-line content in the cell
|
||||
const developerRowMatch = sectionContent.match(
|
||||
@@ -85,40 +120,25 @@ const extractUsernamesFromSection = (sectionContent, groupName) => {
|
||||
);
|
||||
|
||||
if (!developerRowMatch) {
|
||||
logger.warn(`No Developer row found in ${groupName} group section`);
|
||||
return userGroups;
|
||||
throw new Error(`No Developer row found in ${groupName} group section`);
|
||||
}
|
||||
|
||||
const developerCell = developerRowMatch[1];
|
||||
const developerUsernames = extractUsernamesFromCell(developerCell);
|
||||
|
||||
// Extract GitHub usernames from the developer cell
|
||||
// Look for patterns like [@username](https://github.com/username)
|
||||
// Note: This match could fail with slight variations in formatting (extra spaces, different brackets, etc.).
|
||||
const usernameMatches = developerCell.match(/\[@([a-zA-Z0-9-]+)]\([^)]+\)/g);
|
||||
|
||||
if (!usernameMatches) {
|
||||
logger.warn(
|
||||
`No GitHub usernames found in ${groupName} group Developer row`
|
||||
if (developerUsernames.length === 0) {
|
||||
throw new Error(
|
||||
`No developers found in ${groupName} group Developer row`
|
||||
);
|
||||
return userGroups;
|
||||
}
|
||||
|
||||
const usernames = usernameMatches
|
||||
.map((match) => {
|
||||
// Extract username from _([@username](url))_ format
|
||||
const usernameMatch = match.match(/\[@([a-zA-Z0-9-]+)]/);
|
||||
return usernameMatch ? usernameMatch[1] : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
|
||||
logger.info(
|
||||
`Found ${usernames.length
|
||||
} developers in ${groupName} group: ${usernames.join(', ')}`
|
||||
`Found ${developerUsernames.length} developer(s) in ${groupName} group: ${developerUsernames.join(', ')}`
|
||||
);
|
||||
|
||||
// Create user group mappings for both the specific group and engineering
|
||||
for (const username of usernames) {
|
||||
// Add to specific group (mdm, orchestration, software)
|
||||
for (const username of developerUsernames) {
|
||||
// Add to specific group (mdm, orchestration, software, security-compliance)
|
||||
userGroups.push({ group: groupName, username });
|
||||
|
||||
// Add to engineering group (all developers are in engineering)
|
||||
@@ -128,6 +148,30 @@ const extractUsernamesFromSection = (sectionContent, groupName) => {
|
||||
return userGroups;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts GitHub usernames from a table cell
|
||||
* @param {string} cellContent - Content of the table cell
|
||||
* @returns {Array<string>} Array of GitHub usernames
|
||||
*/
|
||||
const extractUsernamesFromCell = (cellContent) => {
|
||||
// Extract GitHub usernames from the cell
|
||||
// Look for patterns like [@username](https://github.com/username)
|
||||
// Note: This match could fail with slight variations in formatting (extra spaces, different brackets, etc.).
|
||||
const usernameMatches = cellContent.match(/\[@([a-zA-Z0-9-]+)]\([^)]+\)/g);
|
||||
|
||||
if (!usernameMatches) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return usernameMatches
|
||||
.map((match) => {
|
||||
// Extract username from _([@username](url))_ format
|
||||
const usernameMatch = match.match(/\[@([a-zA-Z0-9-]+)]/);
|
||||
return usernameMatch ? usernameMatch[1] : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the structure of the markdown content
|
||||
* @param {string} content - Markdown content to validate
|
||||
@@ -138,6 +182,7 @@ export const validateMarkdownStructure = (content) => {
|
||||
'MDM group',
|
||||
'Orchestration group',
|
||||
'Software group',
|
||||
'Security & compliance group',
|
||||
];
|
||||
|
||||
for (const section of requiredSections) {
|
||||
|
||||
@@ -60,6 +60,7 @@ describe('MarkdownParser', () => {
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Product Manager | _([@testpm1](https://github.com/testpm1))_ |
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Developer | _([@testdev1](https://github.com/testdev1))_, _([@testdev2](https://github.com/testdev2))_, _([@testdev3](https://github.com/testdev3))_ |
|
||||
| Quality Assurance | _([@testqa1](https://github.com/testqa1))_ |
|
||||
|
||||
@@ -68,14 +69,23 @@ describe('MarkdownParser', () => {
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Product Manager | _([@testpm2](https://github.com/testpm2))_ |
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_, _([@orchdev2](https://github.com/orchdev2))_, _([@orchdev3](https://github.com/orchdev3))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_, _([@softdev2](https://github.com/softdev2))_ |
|
||||
| Quality Assurance | _([@testqa2](https://github.com/testqa2))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_, _([@secdev2](https://github.com/secdev2))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
@@ -88,35 +98,53 @@ describe('MarkdownParser', () => {
|
||||
|
||||
// Should extract usernames and create dual group membership
|
||||
expect(result).toEqual([
|
||||
// MDM group users
|
||||
// MDM group users (tech lead + developers)
|
||||
{ group: 'mdm', username: 'techlead1' },
|
||||
{ group: 'engineering', username: 'techlead1' },
|
||||
{ group: 'mdm', username: 'testdev1' },
|
||||
{ group: 'engineering', username: 'testdev1' },
|
||||
{ group: 'mdm', username: 'testdev2' },
|
||||
{ group: 'engineering', username: 'testdev2' },
|
||||
{ group: 'mdm', username: 'testdev3' },
|
||||
{ group: 'engineering', username: 'testdev3' },
|
||||
// Orchestration group users
|
||||
// Orchestration group users (tech lead + developers)
|
||||
{ group: 'orchestration', username: 'techlead2' },
|
||||
{ group: 'engineering', username: 'techlead2' },
|
||||
{ group: 'orchestration', username: 'orchdev1' },
|
||||
{ group: 'engineering', username: 'orchdev1' },
|
||||
{ group: 'orchestration', username: 'orchdev2' },
|
||||
{ group: 'engineering', username: 'orchdev2' },
|
||||
{ group: 'orchestration', username: 'orchdev3' },
|
||||
{ group: 'engineering', username: 'orchdev3' },
|
||||
// Software group users
|
||||
// Software group users (tech lead + developers)
|
||||
{ group: 'software', username: 'techlead3' },
|
||||
{ group: 'engineering', username: 'techlead3' },
|
||||
{ group: 'software', username: 'softdev1' },
|
||||
{ group: 'engineering', username: 'softdev1' },
|
||||
{ group: 'software', username: 'softdev2' },
|
||||
{ group: 'engineering', username: 'softdev2' }
|
||||
{ group: 'engineering', username: 'softdev2' },
|
||||
// Security & compliance group users (tech lead + developers)
|
||||
{ group: 'security-compliance', username: 'techlead4' },
|
||||
{ group: 'engineering', username: 'techlead4' },
|
||||
{ group: 'security-compliance', username: 'secdev1' },
|
||||
{ group: 'engineering', username: 'secdev1' },
|
||||
{ group: 'security-compliance', username: 'secdev2' },
|
||||
{ group: 'engineering', username: 'secdev2' }
|
||||
]);
|
||||
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Parsing product groups from /resolved/test-file.md');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 3 developers in mdm group: testdev1, testdev2, testdev3');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 3 developers in orchestration group: orchdev1, orchdev2, orchdev3');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 developers in software group: softdev1, softdev2');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Extracted 16 user-group mappings from markdown');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tech lead(s) in mdm group: techlead1');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 3 developer(s) in mdm group: testdev1, testdev2, testdev3');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tech lead(s) in orchestration group: techlead2');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 3 developer(s) in orchestration group: orchdev1, orchdev2, orchdev3');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tech lead(s) in software group: techlead3');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 developer(s) in software group: softdev1, softdev2');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 1 tech lead(s) in security-compliance group: techlead4');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Found 2 developer(s) in security-compliance group: secdev1, secdev2');
|
||||
expect(mockLogger.info).toHaveBeenCalledWith('Extracted 28 user-group mappings from markdown');
|
||||
});
|
||||
|
||||
it('should handle missing sections gracefully', () => {
|
||||
it('should throw error when sections are missing', () => {
|
||||
const mockMarkdown = `
|
||||
# Product Groups
|
||||
|
||||
@@ -124,12 +152,14 @@ describe('MarkdownParser', () => {
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Developer | _([@testdev1](https://github.com/testdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
`;
|
||||
|
||||
@@ -138,17 +168,11 @@ describe('MarkdownParser', () => {
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ group: 'mdm', username: 'testdev1' },
|
||||
{ group: 'engineering', username: 'testdev1' },
|
||||
{ group: 'software', username: 'softdev1' },
|
||||
{ group: 'engineering', username: 'softdev1' }
|
||||
]);
|
||||
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('Section not found: Orchestration group');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Section not found: Orchestration group');
|
||||
});
|
||||
|
||||
it('should handle missing Developer rows', () => {
|
||||
it('should throw error when Tech Lead row is missing', () => {
|
||||
const mockMarkdown = `
|
||||
# Product Groups
|
||||
|
||||
@@ -157,13 +181,29 @@ describe('MarkdownParser', () => {
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Product Manager | _([@testpm1](https://github.com/testpm1))_ |
|
||||
| Developer | _([@testdev1](https://github.com/testdev1))_ |
|
||||
| Quality Assurance | _([@testqa1](https://github.com/testqa1))_ |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
@@ -171,15 +211,11 @@ describe('MarkdownParser', () => {
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ group: 'orchestration', username: 'orchdev1' },
|
||||
{ group: 'engineering', username: 'orchdev1' }
|
||||
]);
|
||||
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('No Developer row found in mdm group section');
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error extracting usernames from MDM group', {}, expect.any(Error));
|
||||
});
|
||||
|
||||
it('should handle malformed GitHub username patterns', () => {
|
||||
it('should throw error when Developer row is missing', () => {
|
||||
const mockMarkdown = `
|
||||
# Product Groups
|
||||
|
||||
@@ -187,13 +223,30 @@ describe('MarkdownParser', () => {
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Developer | Some text without proper format, _([@testvaliduser](https://github.com/testvaliduser))_, invalid format here |
|
||||
| Product Manager | _([@testpm1](https://github.com/testpm1))_ |
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Quality Assurance | _([@testqa1](https://github.com/testqa1))_ |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Developer | No valid usernames here at all |
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
@@ -201,12 +254,92 @@ describe('MarkdownParser', () => {
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ group: 'mdm', username: 'testvaliduser' },
|
||||
{ group: 'engineering', username: 'testvaliduser' }
|
||||
]);
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error extracting usernames from MDM group', {}, expect.any(Error));
|
||||
});
|
||||
|
||||
expect(mockLogger.warn).toHaveBeenCalledWith('No GitHub usernames found in orchestration group Developer row');
|
||||
it('should throw error when no valid developer usernames found', () => {
|
||||
const mockMarkdown = `
|
||||
# Product Groups
|
||||
|
||||
### MDM group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Developer | Some text without proper format, _([@testvaliduser](https://github.com/testvaliduser))_, invalid format here |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | No valid usernames here at all |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
mockFs.readFileSync.mockReturnValue(mockMarkdown);
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error extracting usernames from Orchestration group', {}, expect.any(Error));
|
||||
});
|
||||
|
||||
it('should throw error when no Tech Lead usernames found', () => {
|
||||
const mockMarkdown = `
|
||||
# Product Groups
|
||||
|
||||
### MDM group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | No valid usernames here |
|
||||
| Developer | _([@testdev1](https://github.com/testdev1))_ |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
mockFs.readFileSync.mockReturnValue(mockMarkdown);
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([]);
|
||||
expect(mockLogger.error).toHaveBeenCalledWith('Error extracting usernames from MDM group', {}, expect.any(Error));
|
||||
});
|
||||
|
||||
it('should handle multi-line developer cells', () => {
|
||||
@@ -217,9 +350,31 @@ describe('MarkdownParser', () => {
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead1](https://github.com/techlead1))_ |
|
||||
| Developer | _([@testuser1](https://github.com/testuser1))_,
|
||||
_([@testuser2](https://github.com/testuser2))_,
|
||||
_([@testuser3](https://github.com/testuser3))_ |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
@@ -227,14 +382,14 @@ _([@testuser3](https://github.com/testuser3))_ |
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ group: 'mdm', username: 'testuser1' },
|
||||
{ group: 'engineering', username: 'testuser1' },
|
||||
{ group: 'mdm', username: 'testuser2' },
|
||||
{ group: 'engineering', username: 'testuser2' },
|
||||
{ group: 'mdm', username: 'testuser3' },
|
||||
{ group: 'engineering', username: 'testuser3' }
|
||||
]);
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'techlead1' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'techlead1' });
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'testuser1' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'testuser1' });
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'testuser2' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'testuser2' });
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'testuser3' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'testuser3' });
|
||||
});
|
||||
|
||||
it('should handle file not found', () => {
|
||||
@@ -270,7 +425,29 @@ _([@testuser3](https://github.com/testuser3))_ |
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead-123](https://github.com/techlead-123))_ |
|
||||
| Developer | _([@testuser-123](https://github.com/testuser-123))_, _([@fakeuser-456](https://github.com/fakeuser-456))_ |
|
||||
|
||||
### Orchestration group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead2](https://github.com/techlead2))_ |
|
||||
| Developer | _([@orchdev1](https://github.com/orchdev1))_ |
|
||||
|
||||
### Software group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead3](https://github.com/techlead3))_ |
|
||||
| Developer | _([@softdev1](https://github.com/softdev1))_ |
|
||||
|
||||
### Security & compliance group
|
||||
|
||||
| Role | Contributor |
|
||||
|------|-------------|
|
||||
| Tech Lead | _([@techlead4](https://github.com/techlead4))_ |
|
||||
| Developer | _([@secdev1](https://github.com/secdev1))_ |
|
||||
`;
|
||||
|
||||
mockFs.existsSync.mockReturnValue(true);
|
||||
@@ -278,12 +455,12 @@ _([@testuser3](https://github.com/testuser3))_ |
|
||||
|
||||
const result = parseProductGroups('test-file.md');
|
||||
|
||||
expect(result).toEqual([
|
||||
{ group: 'mdm', username: 'testuser-123' },
|
||||
{ group: 'engineering', username: 'testuser-123' },
|
||||
{ group: 'mdm', username: 'fakeuser-456' },
|
||||
{ group: 'engineering', username: 'fakeuser-456' }
|
||||
]);
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'techlead-123' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'techlead-123' });
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'testuser-123' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'testuser-123' });
|
||||
expect(result).toContainEqual({ group: 'mdm', username: 'fakeuser-456' });
|
||||
expect(result).toContainEqual({ group: 'engineering', username: 'fakeuser-456' });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -300,6 +477,9 @@ Some content
|
||||
|
||||
### Software group
|
||||
Some content
|
||||
|
||||
### Security & compliance group
|
||||
Some content
|
||||
`;
|
||||
|
||||
const result = validateMarkdownStructure(validMarkdown);
|
||||
@@ -342,6 +522,9 @@ Some content
|
||||
|
||||
### software group
|
||||
Some content
|
||||
|
||||
### security & compliance group
|
||||
Some content
|
||||
`;
|
||||
|
||||
const result = validateMarkdownStructure(invalidMarkdown);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user