Files
api-collections/collections/IRIS GW (Gateway) REST API (v1)/Contracts/SCRIPT - Get Contracts with two or more contributors.yml
T

265 lines
11 KiB
YAML

info:
name: SCRIPT - Get Contracts with two or more contributors
type: http
seq: 5
http:
method: GET
url: "{{IrisApiUrl}}/contracts;full?year=2026"
params:
- name: year
value: "2026"
type: query
- name: wfState
value: signed
type: query
disabled: true
- name: page
value: "1"
type: query
disabled: true
auth: inherit
runtime:
scripts:
- type: after-response
code: |-
// Aggregates contracts with 2+ contributors across all pages and reports summary.
// // Fixes TypeError: pm.request.url.query.toObject(...).find is not a function by avoiding Array.prototype.find on toObject() result.
// Adds robust helpers for working with query params across Postman SDK versions and edge cases.
//
// Requirements addressed:
// 1) Read Page-Count header to know total pages
// 2) Iteratively call same endpoint for pages from current+1 to Page-Count, preserving other query params
// 3) Aggregate items where contributorSet exists and has length >= 2
// 4) Build a map pid -> contributorCount across all pages (including initial)
// 5) Print summary in Test Results and set env var `multiContributorContracts` with [{ pid, contributorCount }]
// 6) Robust error handling, rate limiting, early stop on non-2xx; handle JSON array or paginated object response shapes
// 7) Augment aggregation with department (ownerSet[0].organizationUnit.idAb + " - " + description). Keep first non-empty.
(function () {
const RATE_LIMIT_DELAY_MS = 200; // small delay between page fetches
const TARGET_ENV_VAR = 'multiContributorContracts';
const PAGE_PARAM = 'page';
// ---------------- URL and Query helpers (SDK-safe) -----------------
// Returns a plain object of query params. Works with:
// - pm.request.url.query (SDK v8+ as QueryList) using .toObject()
// - URL that has no query or is a raw string
function getQueryObject(url) {
try {
// // pm.request.url can be a Url object or a string. Normalize to raw string.
const raw = (typeof url === 'string') ? url : (url && (url.toString ? url.toString() : url.raw || ''));
// Try SDK path first if it's a Url object with .query
if (url && url.query && typeof url.query.toObject === 'function') {
const obj = url.query.toObject();
// toObject may return undefined/null on empty query
return obj && typeof obj === 'object' ? { ...obj } : {};
}
// Fallback: parse the raw string
if (typeof raw === 'string') {
const qIndex = raw.indexOf('?');
if (qIndex === -1) return {};
const queryStr = raw.substring(qIndex + 1);
if (!queryStr) return {};
return queryStr.split('&').reduce((acc, pair) => {
if (!pair) return acc;
const [k, v] = pair.split('=');
if (!k) return acc;
acc[decodeURIComponent(k)] = v !== undefined ? decodeURIComponent(v) : '';
return acc;
}, {});
}
} catch (e) {
// fallthrough
}
return {};
}
function setQueryObject(url, updates) {
// Returns a new raw URL string with given query params merged
const raw = (typeof url === 'string') ? url : (url && (url.toString ? url.toString() : url.raw || ''));
const qIndex = raw.indexOf('?');
const base = qIndex === -1 ? raw : raw.substring(0, qIndex);
const current = getQueryObject(url);
const merged = { ...current, ...updates };
// Filter out empty/undefined to avoid adding stray keys
const parts = Object.keys(merged)
.filter(k => merged[k] !== undefined && merged[k] !== null && merged[k] !== '')
.map(k => encodeURIComponent(k) + '=' + encodeURIComponent(String(merged[k])));
return parts.length ? base + '?' + parts.join('&') : base;
}
// ---------------- Response shape helpers -----------------
function isObject(x) { return x && typeof x === 'object' && !Array.isArray(x); }
function getItemsFromResponseBody(rb) {
// Supports either an array payload or an object with an array at known keys
if (Array.isArray(rb)) return rb;
if (isObject(rb)) {
// Try common keys: 'items', 'data', 'results'
if (Array.isArray(rb.items)) return rb.items;
if (Array.isArray(rb.data)) return rb.data;
if (Array.isArray(rb.results)) return rb.results;
}
return [];
}
function safeJson(body) {
try { return JSON.parse(body); } catch (e) { return null; }
}
// ---------------- Aggregation store -----------------
// Map: pid -> { contributorCount, department }
const aggregate = {};
function extractDepartment(item) {
try {
const owner0 = Array.isArray(item.ownerSet) && item.ownerSet.length > 0 ? item.ownerSet[0] : null;
const ou = owner0 && owner0.organizationUnit ? owner0.organizationUnit : null;
const idAb = ou && typeof ou.idAb === 'string' ? ou.idAb : null;
const desc = ou && typeof ou.description === 'string' ? ou.description : null;
if (idAb && desc) return idAb + ' - ' + desc;
return '';
} catch (e) {
return '';
}
}
function considerItems(items) {
items.forEach(it => {
const contributors = Array.isArray(it.contributorSet) ? it.contributorSet : [];
if (contributors.length >= 2) {
const pid = (it.pid != null) ? String(it.pid) : '';
if (!pid) return;
const contributorCount = contributors.length;
const dept = extractDepartment(it);
if (!aggregate[pid]) {
aggregate[pid] = { contributorCount, department: dept || '' };
} else {
// keep max contributor count seen (in case of variations) and first non-empty department
aggregate[pid].contributorCount = Math.max(aggregate[pid].contributorCount, contributorCount);
if (!aggregate[pid].department && dept) {
aggregate[pid].department = dept;
}
}
}
});
}
// ---------------- Paging orchestration -----------------
const initialStatus = res.getStatus();
const is2xx = initialStatus >= 200 && initialStatus < 300;
if (!is2xx) {
test('Request failed - not aggregating on non-2xx', function () {
expect(is2xx).to.eql(true);
});
return;
}
const rb = safeJson(JSON.stringify(res.getBody()));
const initialItems = rb ? getItemsFromResponseBody(rb) : [];
considerItems(initialItems);
// Derive total pages from header 'Page-Count' or 'page-count'
const pageCountHeader = res.getHeader('Page-Count') || res.getHeader('page-count') || res.getHeader('X-Total-Pages');
const totalPages = pageCountHeader ? parseInt(pageCountHeader, 10) : 1;
// Figure out current page from request URL (query param 'page')
const currentQuery = getQueryObject(req.getUrl());
const currentPage = parseInt(currentQuery[PAGE_PARAM] || '1', 10) || 1;
// Build a function that fetches page N and aggregates
function fetchPage(n) {
return new Promise((resolve) => {
//console.log("Setting current page to: " + n);
const nextUrl = setQueryObject(req.getUrl(), { [PAGE_PARAM]: String(n) });
const reqHeaders = req.getHeaders();
console.log("Setting headers: " + JSON.stringify(reqHeaders));
setTimeout(async function () {
console.log("Calling next URL... (" + nextUrl + ")");
await bru.sendRequest({ url: nextUrl, method: 'GET', headers: reqHeaders }, async function(err, res) {
if (err || !res) {
console.log("Error fetching page " + n + " (err: " + JSON.stringify(err) + ")");
test('Error fetching page ' + n, function () {
expect(err).to.eql(null);
});
return resolve(false);
}
const ok = res.status >= 200 && res.status < 300;
console.log("Next URL response: " + ok);
if (!ok) {
test('Non-2xx on page ' + n + ' - stop further paging', function () {
expect(ok).to.eql(true);
});
return resolve(false);
}
const body = res.data;
const json = safeJson(body);
const items = json ? getItemsFromResponseBody(json) : [];
considerItems(items);
return resolve(true);
});
}, RATE_LIMIT_DELAY_MS);
});
}
async function run() {
console.log("Running...");
console.log("Total pages found: " + totalPages);
// If there are more pages, iterate
for (let p = currentPage + 1; p <= totalPages; p++) {
const cont = await fetchPage(p);
if (!cont) break;
}
// Prepare output array
const output = Object.keys(aggregate).map(pid => ({
pid,
contributorCount: aggregate[pid].contributorCount,
department: aggregate[pid].department || ''
}));
// Save to environment
bru.setVar(TARGET_ENV_VAR, JSON.stringify(output));
console.log(JSON.stringify(output));
// Basic summary tests
test('Aggregated items have required properties', function () {
output.forEach(item => {
expect(item).to.have.property('pid');
expect(item.pid).to.be.a('string');
expect(item).to.have.property('contributorCount');
expect(item.contributorCount).to.be.a('number');
expect(item).to.have.property('department');
expect(item.department).to.be.a('string');
});
});
// Optional: Log summary count
test('Total multi-contributor contracts aggregated', function () {
expect(output.length).to.be.at.least(0);
});
}
run();
})();
actions:
- type: set-variable
phase: after-response
selector:
expression: ""
method: jsonq
variable:
name: multiContributorContracts
scope: runtime
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5