Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 37 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,42 @@ jobs:
## 🚀 swagger-coverage-cli v${{ env.NEW_VERSION }}

### ✨ New Features
- **📁 Folder-Level Tests Support**: Tests defined at folder level in Postman collections are now automatically applied to all requests within that folder
- **🔄 Recursive Test Inheritance**: Support for nested folder hierarchies with test inheritance across multiple levels
- **🛡️ Flexible Spec Validation**: New `--disable-spec-validation` flag to process specs with validation or reference issues

### 🎨 Enhanced Features
- **Better Coverage Calculation**: More accurate coverage metrics by including folder-level test assertions
- **Reduced Test Duplication**: Define common tests once at folder level instead of repeating on every request
- **Enterprise Collection Support**: Improved support for well-organized Postman collections with folder structures
- **Legacy API Support**: Work with incomplete or invalid specs using `--disable-spec-validation`

### 📁 Folder-Level Tests

Postman collections organized into folders can now have tests defined at the folder level that automatically apply to all child requests:

```json
{
"name": "User API",
"item": [
{ "name": "Get Users", "request": {...} },
{ "name": "Create User", "request": {...} }
],
"event": [{
"listen": "test",
"script": {
"exec": ["pm.expect(pm.response.code).to.be.oneOf([200, 201]);"]
}
}]
}
```

**Benefits:**
- ✅ Both requests inherit status codes 200 and 201 from folder
- ✅ Folder tests combine with request-level tests
- ✅ Supports unlimited nesting depth
- ✅ Backward compatible with existing collections

### 🛡️ Spec Validation Control

The new `--disable-spec-validation` flag allows you to analyze coverage even when specs have validation issues:
Expand Down Expand Up @@ -153,12 +184,13 @@ jobs:
```

### 🧪 Quality Assurance
- **183 Tests**: Comprehensive test suite covering all protocols and scenarios including spec validation control
- **22 Test Suites**: Dedicated test coverage for each protocol, integration scenarios, and validation features
- **Edge Case Coverage**: Robust handling of malformed URLs, missing data, broken references, and complex scenarios
- **Performance Tested**: Validated with large datasets and mixed protocol specifications
- **198 Tests**: Comprehensive test suite covering all protocols, scenarios, folder-level tests, and spec validation control
- **22 Test Suites**: Dedicated test coverage for each protocol, integration scenarios, validation features, and edge cases
- **Folder Tests Coverage**: 18 tests specifically for folder-level test extraction including deep nesting, empty folders, and mixed patterns
- **Edge Case Coverage**: Robust handling of malformed URLs, missing data, broken references, complex scenarios, and various Postman collection structures
- **Performance Tested**: Validated with large datasets, mixed protocol specifications, and deeply nested folder hierarchies
- **Protocol Isolation**: Each protocol's parsing and matching logic is independently tested
- **Validation Testing**: 16 new tests for `--disable-spec-validation` flag covering unit and CLI integration
- **Validation Testing**: Comprehensive tests for `--disable-spec-validation` flag covering unit and CLI integration

---

Expand Down
2 changes: 1 addition & 1 deletion auto-detect-newman.html
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ <h1>Swagger Coverage Report</h1>
&#128262; <!-- flashlight icon -->
</button>
<div class="meta-info">
<p><strong>Timestamp:</strong> 10/9/2025, 8:30:30 AM</p>
<p><strong>Timestamp:</strong> 10/9/2025, 11:28:33 AM</p>
<p><strong>API Spec:</strong> Test API</p>

<p><strong>Postman Collection:</strong> Test Newman Collection</p>
Expand Down
103 changes: 63 additions & 40 deletions lib/postman.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,63 @@ function loadPostmanCollection(filePath) {
function extractRequestsFromPostman(collection, verbose = false) {
const requests = [];

function traverseItems(items, currentFolder = '') {
/**
* Extract status codes and test scripts from events
*/
function extractTestsFromEvents(events) {
const testedStatusCodes = new Set();
let testScripts = '';

if (events && Array.isArray(events)) {
events.forEach(ev => {
if (ev.listen === 'test' && ev.script && ev.script.exec) {
const scriptCode = ev.script.exec.join('\n');
testScripts += scriptCode + '\n'; // Aggregate all test scripts

// Ищем различные паттерны
const patterns = [
/to\.have\.status\((\d+)\)/g,
/pm\.expect\(pm\.response\.code\)\.to\.eql\((\d+)\)/g,
/pm\.response\.code\s*={1,3}\s*(\d+)/g,
/pm\.response\.status\s*={1,3}\s*(\d+)/g,
/pm\.expect\(pm\.response\.code\)\.to\.be\.oneOf\(\[([^\]]+)\]\)/g,
/to\.be\.oneOf\(\[([^\]]+)\]\)/g
];
patterns.forEach(regex => {
let match;
while ((match = regex.exec(scriptCode)) !== null) {
if (regex === patterns[4] || regex === patterns[5]) {
// Extract multiple codes if present (oneOf pattern)
const codesStr = match[1];
const codesArr = codesStr.match(/\d+/g);
if (codesArr) {
codesArr.forEach(c => testedStatusCodes.add(c));
}
} else {
testedStatusCodes.add(match[1]);
}
}
});
}
});
}

return { testedStatusCodes, testScripts };
}

function traverseItems(items, currentFolder = '', folderTests = { testedStatusCodes: new Set(), testScripts: '' }) {
items.forEach(item => {
if (item.item) {
// Это папка
traverseItems(item.item, item.name);
// Это папка - извлекаем тесты из папки
const folderTestData = extractTestsFromEvents(item.event);

// Объединяем тесты папки с родительскими тестами
const combinedFolderTests = {
testedStatusCodes: new Set([...folderTests.testedStatusCodes, ...folderTestData.testedStatusCodes]),
testScripts: folderTests.testScripts + folderTestData.testScripts
};
Comment on lines +81 to +88
Copy link

Copilot AI Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Russian comments should be translated to English for consistency with the rest of the codebase. Consider translating 'Это папка - извлекаем тесты из папки' to 'This is a folder - extract tests from folder' and 'Объединяем тесты папки с родительскими тестами' to 'Combine folder tests with parent tests'.

Copilot uses AI. Check for mistakes.

traverseItems(item.item, item.name, combinedFolderTests);
} else {
// Это запрос
Copy link

Copilot AI Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Russian comment should be translated to English. Consider translating 'Это запрос' to 'This is a request'.

Suggested change
// Это запрос
// This is a request

Copilot uses AI. Check for mistakes.
const req = item.request || {};
Expand All @@ -57,41 +109,12 @@ function extractRequestsFromPostman(collection, verbose = false) {
};
}

// Ищем status-коды в тест-скриптах
let testedStatusCodes = new Set();
let testScripts = '';
if (item.event && Array.isArray(item.event)) {
item.event.forEach(ev => {
if (ev.listen === 'test' && ev.script && ev.script.exec) {
const scriptCode = ev.script.exec.join('\n');
testScripts += scriptCode + '\n'; // Aggregate all test scripts

// Ищем различные паттерны
const patterns = [
/to\.have\.status\((\d+)\)/g,
/pm\.expect\(pm\.response\.code\)\.to\.eql\((\d+)\)/g,
/pm\.response\.code\s*={1,3}\s*(\d+)/g,
/pm\.response\.status\s*={1,3}\s*(\d+)/g,
/pm\.expect\(pm\.response\.code\)\.to\.be\.oneOf\(\[\s*([^]]+)\]/g
];
patterns.forEach(regex => {
let match;
while ((match = regex.exec(scriptCode)) !== null) {
if (regex === patterns[4]) {
// Extract multiple codes if present
const codesStr = match[1];
const codesArr = codesStr.match(/\d+/g);
if (codesArr) {
codesArr.forEach(c => testedStatusCodes.add(c));
}
} else {
testedStatusCodes.add(match[1]);
}
}
});
}
});
}
// Извлекаем тесты из самого запроса
const requestTestData = extractTestsFromEvents(item.event);

// Объединяем тесты запроса с тестами папки
Comment on lines +112 to +115
Copy link

Copilot AI Oct 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Russian comments should be translated to English. Consider translating 'Извлекаем тесты из самого запроса' to 'Extract tests from the request itself' and 'Объединяем тесты запроса с тестами папки' to 'Combine request tests with folder tests'.

Suggested change
// Извлекаем тесты из самого запроса
const requestTestData = extractTestsFromEvents(item.event);
// Объединяем тесты запроса с тестами папки
// Extract tests from the request itself
const requestTestData = extractTestsFromEvents(item.event);
// Combine request tests with folder tests

Copilot uses AI. Check for mistakes.
const combinedStatusCodes = new Set([...folderTests.testedStatusCodes, ...requestTestData.testedStatusCodes]);
const combinedTestScripts = folderTests.testScripts + requestTestData.testScripts;

requests.push({
name: item.name,
Expand All @@ -100,8 +123,8 @@ function extractRequestsFromPostman(collection, verbose = false) {
rawUrl,
queryParams,
bodyInfo,
testedStatusCodes: Array.from(testedStatusCodes),
testScripts: testScripts.trim() // Include aggregated test scripts
testedStatusCodes: Array.from(combinedStatusCodes),
testScripts: combinedTestScripts.trim() // Include aggregated test scripts
});
}
});
Expand Down
76 changes: 76 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ You will need:

> **Note**:
> - If using a Postman collection, make sure it includes actual test scripts that assert or check specific status codes (e.g., `pm.response.to.have.status(200)`).
> - **Folder-level tests** are fully supported: tests defined at the folder level in your Postman collection will be automatically applied to all requests within that folder, ensuring comprehensive coverage calculation.
> - If using a Newman report, the tool will extract actual response codes and test results from the execution data.

### 2. Run the CLI
Expand Down Expand Up @@ -712,6 +713,7 @@ The tool supports two types of input for test data:
- **Pros**:
- Contains all test logic and assertions
- Can extract expected status codes from test scripts
- **Supports folder-level tests**: Tests defined at the folder level are automatically applied to all requests within that folder and its subfolders
- **Cons**:
- No actual execution data
- Relies on parsing test scripts to understand expected outcomes
Expand All @@ -728,6 +730,80 @@ The tool supports two types of input for test data:

**Recommendation**: Use Newman reports when possible for more accurate coverage analysis, especially in CI/CD pipelines where collections are actually executed.

### Folder-Level Tests in Postman Collections

**swagger-coverage-cli** fully supports **folder-level tests** in Postman collections. Tests defined at the folder level are automatically applied to all requests within that folder and its subfolders, making it easy to apply common test assertions across multiple endpoints.

#### How It Works

When you define tests at the folder level in your Postman collection:
1. The tests are extracted from the folder's event scripts
2. Status codes and assertions are identified from the test scripts
3. These tests are automatically combined with any request-level tests
4. All requests within the folder (and nested folders) inherit these tests

#### Example

Consider this Postman collection structure:

```json
{
"name": "Users API",
"item": [
{
"name": "Get User",
"request": { "method": "GET", "url": "/users/1" }
},
{
"name": "Create User",
"request": { "method": "POST", "url": "/users" }
}
],
"event": [
{
"listen": "test",
"script": {
"exec": [
"pm.test('Status code is 200 or 201', function () {",
" pm.expect(pm.response.code).to.be.oneOf([200, 201]);",
"});"
]
}
}
]
}
```

In this example:
- The folder "Users API" has a test that checks for status codes 200 or 201
- **Both** "Get User" and "Create User" requests will inherit these status codes
- This means both endpoints will be counted as testing status codes 200 and 201

#### Benefits

- **Reduced duplication**: Define common tests once at the folder level
- **Better coverage**: Ensures all requests within a folder test common scenarios
- **Easier maintenance**: Update folder-level tests to affect all child requests
- **Nested support**: Folder-level tests are inherited through multiple levels of nesting

#### Supported Test Patterns

Folder-level tests support the same patterns as request-level tests:
- `pm.response.to.have.status(200)`
- `pm.expect(pm.response.code).to.eql(201)`
- `pm.expect(pm.response.code).to.be.oneOf([200, 201, 204])`
- And other common Postman test assertions

#### Edge Cases Handled

The folder-level test feature handles various edge cases:
- **Empty folders**: Folders with no requests are handled gracefully
- **Deep nesting**: Supports unlimited levels of nested folders with test inheritance at each level
- **Missing events**: Requests or folders without event properties work correctly
- **Mixed patterns**: Multiple test patterns in the same folder or request are combined
- **Null/undefined events**: Folders with null or undefined event properties are handled safely
- **Non-test events**: Only "test" events are processed; other events (like "prerequest") are ignored

### Using CSV for Documentation

In addition to traditional OpenAPI/Swagger specifications, **swagger-coverage-cli** supports API documentation provided in a **CSV** format. This allows for a more flexible and easily editable documentation process, especially for teams that prefer spreadsheet-based documentation.
Expand Down
Loading