From 6eb1eb2a68cd285012c2796b1ef1adc980b2317c Mon Sep 17 00:00:00 2001 From: Iulia Grumaz Date: Mon, 19 Aug 2024 16:58:06 +0300 Subject: [PATCH] feat(costs): add costs audit that returns the ahrefs API units consumed and limit --- package-lock.json | 6 +-- package.json | 2 +- src/costs/handler.js | 51 +++++++++++++++++++ src/index.js | 2 + test/audits/costs.test.js | 103 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 src/costs/handler.js create mode 100644 test/audits/costs.test.js diff --git a/package-lock.json b/package-lock.json index 6b5989d7..ddd10415 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@adobe/helix-status": "10.1.2", "@adobe/helix-universal": "5.0.5", "@adobe/helix-universal-logger": "3.0.18", - "@adobe/spacecat-shared-ahrefs-client": "1.4.1", + "@adobe/spacecat-shared-ahrefs-client": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-ahrefs-client?ahrefs-cost", "@adobe/spacecat-shared-data-access": "1.41.4", "@adobe/spacecat-shared-http-utils": "1.6.5", "@adobe/spacecat-shared-rum-api-client": "2.7.3", @@ -1521,8 +1521,8 @@ }, "node_modules/@adobe/spacecat-shared-ahrefs-client": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@adobe/spacecat-shared-ahrefs-client/-/spacecat-shared-ahrefs-client-1.4.1.tgz", - "integrity": "sha512-SfYqzaalkHFQRPRP/+twFRXgixoczGMXWUOpkyyZYxy3ireHyO7u7qfS9Jg2TRtmG1XUuxxU2LScr3fBs95v9w==", + "resolved": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-ahrefs-client?ahrefs-cost", + "integrity": "sha512-wGJHvPInNisEf71/i+QBqz5IHZ5BmV2mLhpustZlROUfYaaJYvzyaxtdsrwLlVhOUAsQArKh/1TkvZfItF3j5g==", "license": "Apache-2.0", "dependencies": { "@adobe/fetch": "4.1.8", diff --git a/package.json b/package.json index 3f99c3d7..5121fc18 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@adobe/helix-status": "10.1.2", "@adobe/helix-universal": "5.0.5", "@adobe/helix-universal-logger": "3.0.18", - "@adobe/spacecat-shared-ahrefs-client": "1.4.1", + "@adobe/spacecat-shared-ahrefs-client": "https://gitpkg.now.sh/adobe/spacecat-shared/packages/spacecat-shared-ahrefs-client?ahrefs-cost", "@adobe/spacecat-shared-data-access": "1.41.4", "@adobe/spacecat-shared-http-utils": "1.6.5", "@adobe/spacecat-shared-rum-api-client": "2.7.3", diff --git a/src/costs/handler.js b/src/costs/handler.js new file mode 100644 index 00000000..0b7427cf --- /dev/null +++ b/src/costs/handler.js @@ -0,0 +1,51 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import AhrefsAPIClient from '@adobe/spacecat-shared-ahrefs-client'; +import { AuditBuilder } from '../common/audit-builder.js'; + +export async function runner(auditUrl, context) { + const { log } = context; + const ahrefsAPIClient = AhrefsAPIClient.createFrom(context); + + let ahrefsCostsAuditResult; + try { + const { + result, + fullAuditRef, + } = await ahrefsAPIClient.getLimitsAndUsage(); + + log.info(`Retrieved Ahrefs limits and usage: ${JSON.stringify(result)}`); + ahrefsCostsAuditResult = { + usedApiUnits: result?.limits_and_usage?.units_usage_api_key, + limitApiUnits: result?.limits_and_usage?.units_limit_api_key, + fullAuditRef, + }; + } catch (e) { + log.error(`Ahrefs costs type audit failed with error: ${e.message}`, e); + ahrefsCostsAuditResult = { + error: `Ahrefs costs type audit failed with error: ${e.message}`, + }; + } + + return { + auditResult: { + ahrefs: ahrefsCostsAuditResult, + }, + fullAuditRef: ahrefsCostsAuditResult?.fullAuditRef, + }; +} + +export default new AuditBuilder() + .withRunner(runner) + .withMessageSender(() => {}) + .build(); diff --git a/src/index.js b/src/index.js index a51ef9b0..ca3a21e3 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,7 @@ import experimentation from './experimentation/handler.js'; import conversion from './conversion/handler.js'; import essExperimentationDaily from './experimentation-ess/daily.js'; import essExperimentationAll from './experimentation-ess/all.js'; +import costs from './costs/handler.js'; const HANDLERS = { apex, @@ -43,6 +44,7 @@ const HANDLERS = { conversion, 'experimentation-ess-daily': essExperimentationDaily, 'experimentation-ess-all': essExperimentationAll, + costs, }; function getElapsedSeconds(startTime) { diff --git a/test/audits/costs.test.js b/test/audits/costs.test.js new file mode 100644 index 00000000..fb564c81 --- /dev/null +++ b/test/audits/costs.test.js @@ -0,0 +1,103 @@ +/* + * Copyright 2024 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +/* eslint-env mocha */ +import chai from 'chai'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import chaiAsPromised from 'chai-as-promised'; +import nock from 'nock'; +import { MockContextBuilder } from '../shared.js'; +import { runner } from '../../src/costs/handler.js'; + +chai.use(sinonChai); +chai.use(chaiAsPromised); +const { expect } = chai; + +const message = { + type: 'costs', + url: 'site-id', +}; +const sandbox = sinon.createSandbox(); + +describe('Costs audit', () => { + let context; + + beforeEach('setup', () => { + context = new MockContextBuilder() + .withSandbox(sandbox) + .withOverrides({ + env: { + AHREFS_API_BASE_URL: 'https://ahrefs-example.com', + AHREFS_API_KEY: 'ahrefs-token', + }, + }) + .build(message); + }); + + afterEach(() => { + nock.cleanAll(); + sandbox.restore(); + }); + + it('costs audit returns ahrefs costs succesfully', async () => { + const limitsUsageResponse = { + limits_and_usage: { + subscription: 'Enterprise, billed yearly', + usage_reset_date: '2024-08-28T00:00:00Z', + units_limit_workspace: 12000000, + units_usage_workspace: 6618294, + units_limit_api_key: 1000000, + units_usage_api_key: 198771, + api_key_expiration_date: '2025-01-04T17:44:07Z', + }, + }; + + nock('https://ahrefs-example.com') + .get('/subscription-info/limits-and-usage') + .reply(200, limitsUsageResponse); + + const result = await runner('https://spacecat.com', context); + + const expectedAuditResult = { + ahrefs: { + usedApiUnits: 198771, + limitApiUnits: 1000000, + fullAuditRef: 'https://ahrefs-example.com/subscription-info/limits-and-usage', + }, + }; + + expect(result).to.eql({ + auditResult: expectedAuditResult, + fullAuditRef: 'https://ahrefs-example.com/subscription-info/limits-and-usage', + }); + }); + + it('costs audit returns error for ahrefs costs when call to ahrefs throws', async () => { + nock('https://ahrefs-example.com') + .get('/subscription-info/limits-and-usage') + .reply(500); + + const result = await runner('https://spacecat.com', context); + + const expectedAuditResult = { + ahrefs: { + error: 'Ahrefs costs type audit failed with error: Ahrefs API request failed with status: 500', + }, + }; + + expect(result).to.eql({ + auditResult: expectedAuditResult, + fullAuditRef: undefined, + }); + }); +});