Skip to content

Commit

Permalink
Add script to test live API integration (#128)
Browse files Browse the repository at this point in the history
This new `npm run check-integration` script sends some metrics and distributions to Datadog using this library, then queries the API for those same metric values to make sure they were successfully ingested and will be visible in Datadog's UI.

The goal here is to provide a minimal check of real, live integration in case a change to the actual reporter or Datadog client breaks or mis-formats something in a way that our unit tests don’t effectively catch. In *most* cases, our unit tests should be enough (this is why we use nock instead of just mocking the Datadog client), so this check is pretty lightweight (it only checks one metric type, for example).

This also moves some other test tools as scripts from the `test` to the `test-other` directory so they don’t get mixed up with unit tests.
  • Loading branch information
Mr0grog authored Nov 20, 2024
1 parent 56875f5 commit 828358f
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 2 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,29 @@ jobs:
run: |
npm test
test-integration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.default_node_version }}
cache: 'npm'
cache-dependency-path: '**/package.json'

- name: Install dependencies
run: npm install

- name: Check Integration
env:
DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }}
DATADOG_APP_KEY: ${{ secrets.DATADOG_APP_KEY }}
DATADOG_SITE: ${{ secrets.DATADOG_API_HOST }}
run: |
npm run check-integration
lint:
runs-on: ubuntu-latest
steps:
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
.DS_Store

# Logs
logs
*.log

# IDE
.idea
.vscode

# Runtime data
pids
Expand Down Expand Up @@ -39,6 +42,7 @@ package-lock.json

# Users Environment Variables
.lock-wscript
.env

# Scratch working files
scratch.*
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@
"scripts": {
"prepack": "npm run clean && npm run build-types && npm run check-types",
"test": "mocha --reporter spec",
"check-integration": "node test-other/integration_check.js",
"check-codestyle": "npx eslint .",
"check-text": "test/lint_text.sh",
"check-text": "test-other/lint_text.sh",
"build-types": "tsc --build",
"check-types": "tsc --noEmit --strict test/types_check.ts",
"check-types": "tsc --noEmit --strict test-other/types_check.ts",
"clean": "tsc --build --clean"
},
"keywords": [
Expand Down
140 changes: 140 additions & 0 deletions test-other/integration_check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* A basic test of our complete integration with Datadog. This check sends some
* metrics, then queries to make sure they actually got ingested correctly by
* Datadog and will show up as expected.
*/

'use strict';

const { client, v1 } = require('@datadog/datadog-api-client');
const datadogMetrics = require('..');

function floorTo(value, points) {
const factor = 10 ** points;
return Math.round(value * factor) / factor;
}

// Remove when upgrading to Node.js 16; this is built-in (node:times/promises).
function sleep(milliseconds) {
return new Promise(r => setTimeout(r, milliseconds));
}

// Make timestamps round seconds for ease of comparison.
const NOW = floorTo(Date.now(), -3);
const MINUTE = 60 * 1000;

// How long to keep querying for the metric before giving up.
const MAX_WAIT_TIME = 2.5 * MINUTE;
// How long to wait between checks.
const CHECK_INTERVAL_SECONDS = 15;

const testPoints = [
[NOW - 60 * 1000, floorTo(10 * Math.random(), 1)],
[NOW - 30 * 1000, floorTo(10 * Math.random(), 1)],
];

const testMetrics = [
{
type: 'gauge',
name: 'node.datadog.metrics.test.gauge',
tags: ['test-tag-1'],
},
{
type: 'distribution',
name: 'node.datadog.metrics.test.dist',
tags: ['test-tag-2'],
},
];

async function main() {
datadogMetrics.init({ flushIntervalSeconds: 0 });

for (const metric of testMetrics) {
await sendMetric(metric);
}

await sleep(5000);

for (const metric of testMetrics) {
const result = await waitForSentMetric(metric);

if (!result) {
process.exitCode = 1;
}
}
}

async function sendMetric(metric) {
console.log(`Sending random points for ${metric.type} "${metric.name}"`);

for (const [timestamp, value] of testPoints) {
datadogMetrics[metric.type](metric.name, value, metric.tags, timestamp);
await new Promise((resolve, reject) => {
datadogMetrics.flush(resolve, reject);
});
}
}

async function queryMetric(metric) {
const configuration = client.createConfiguration({
authMethods: {
apiKeyAuth: process.env.DATADOG_API_KEY,
appKeyAuth: process.env.DATADOG_APP_KEY,
},
});
configuration.setServerVariables({ site: process.env.DATADOG_SITE });
const metricsApi = new v1.MetricsApi(configuration);

// NOTE: Query timestamps are seconds, but result points are milliseconds.
const data = await metricsApi.queryMetrics({
from: Math.floor((NOW - 5 * MINUTE) / 1000),
to: Math.ceil(Date.now() / 1000),
query: `${metric.name}{${metric.tags[0]}}`,
});

return data.series && data.series[0];
}

async function waitForSentMetric(metric) {
const endTime = Date.now() + MAX_WAIT_TIME;
while (Date.now() < endTime) {
console.log(`Querying Datadog for sent points in ${metric.type} "${metric.name}"...`);
const series = await queryMetric(metric);

if (series) {
const found = testPoints.every(([timestamp, value]) => {
return series.pointlist.some(([remoteTimestamp, remoteValue]) => {
// Datadog may round values differently or place them into
// time intervals based on the metric's configuration. Look
// for timestamp/value combinations that are close enough.
return (
Math.abs(remoteTimestamp - timestamp) < 10000 &&
Math.abs(remoteValue - value) < 0.1
);
});
});

if (found) {
console.log('✔︎ Found sent points! Test passed.');
return true;
} else {
console.log(' Found series, but with no matching points.');
console.log(` Looking for: ${JSON.stringify(testPoints)}`);
console.log(' Found:', JSON.stringify(series, null, 2));
}
}

console.log(` Nothing found, waiting ${CHECK_INTERVAL_SECONDS}s before trying again.`);
await sleep(CHECK_INTERVAL_SECONDS * 1000);
}

console.log('✘ Nothing found and gave up waiting. Test failed!');
return false;
}

if (require.main === module) {
main().catch(error => {
process.exitCode = 1;
console.error(error);
});
}
File renamed without changes.
File renamed without changes.

0 comments on commit 828358f

Please sign in to comment.