From 86543938fb4528bed04c86ce32d1750ee8aa40ec Mon Sep 17 00:00:00 2001
From: Sergio Zharinov <zharinov@users.noreply.github.com>
Date: Thu, 30 Jan 2020 12:51:51 +0400
Subject: [PATCH] feat(cdnjs): Add caching for CDNJS datasource (#5259)

---
 lib/constants/data-binary-source.ts           |  2 +-
 lib/datasource/cdnjs/index.ts                 | 17 ++++++-
 test/datasource/cdnjs.spec.ts                 | 44 +++++--------------
 test/datasource/cdnjs/_fixtures/bulma.json    | 17 -------
 test/datasource/cdnjs/_fixtures/d3-force.json | 20 ---------
 5 files changed, 28 insertions(+), 72 deletions(-)

diff --git a/lib/constants/data-binary-source.ts b/lib/constants/data-binary-source.ts
index 9f6d528d57..46021a4454 100644
--- a/lib/constants/data-binary-source.ts
+++ b/lib/constants/data-binary-source.ts
@@ -1,6 +1,6 @@
 // Datasource
 export const DATASOURCE_CARGO = 'cargo';
-// export const DATASOURCE_CDNJS = 'cdnjs';
+export const DATASOURCE_CDNJS = 'cdnjs';
 export const DATASOURCE_DART = 'dart';
 export const DATASOURCE_DOCKER = 'docker';
 export const DATASOURCE_GIT_SUBMODULES = 'gitSubmodules';
diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts
index acac254aa0..7184c8f06f 100644
--- a/lib/datasource/cdnjs/index.ts
+++ b/lib/datasource/cdnjs/index.ts
@@ -2,12 +2,16 @@ import { logger } from '../../logger';
 import got from '../../util/got';
 import { ReleaseResult, PkgReleaseConfig } from '../common';
 import { DATASOURCE_FAILURE } from '../../constants/error-messages';
+import { DATASOURCE_CDNJS } from '../../constants/data-binary-source';
 
 interface CdnjsAsset {
   version: string;
   files: string[];
 }
 
+const cacheNamespace = `datasource-${DATASOURCE_CDNJS}`;
+const cacheMinutes = 60;
+
 interface CdnjsResponse {
   homepage?: string;
   repository?: {
@@ -28,7 +32,16 @@ export async function getPkgReleases({
 
   const [depName, ...assetParts] = lookupName.split('/');
   const assetName = assetParts.join('/');
-  const url = `https://api.cdnjs.com/libraries/${depName}`;
+
+  const cacheKey = depName;
+  const cachedResult = await renovateCache.get<ReleaseResult>(
+    cacheNamespace,
+    cacheKey
+  );
+  // istanbul ignore if
+  if (cachedResult) return cachedResult;
+
+  const url = `https://api.cdnjs.com/libraries/${depName}?fields=homepage,repository,assets`;
 
   try {
     const res = await got(url, { json: true });
@@ -51,6 +64,8 @@ export async function getPkgReleases({
     if (homepage) result.homepage = homepage;
     if (repository && repository.url) result.sourceUrl = repository.url;
 
+    await renovateCache.set(cacheNamespace, cacheKey, result, cacheMinutes);
+
     return result;
   } catch (err) {
     const errorData = { depName, err };
diff --git a/test/datasource/cdnjs.spec.ts b/test/datasource/cdnjs.spec.ts
index 30991b70c7..f6c9d4262c 100644
--- a/test/datasource/cdnjs.spec.ts
+++ b/test/datasource/cdnjs.spec.ts
@@ -3,7 +3,8 @@ import _got from '../../lib/util/got';
 import { getPkgReleases } from '../../lib/datasource/cdnjs';
 import { DATASOURCE_FAILURE } from '../../lib/constants/error-messages';
 
-const got: any = _got;
+const got: jest.Mock<any> = _got as any;
+jest.mock('../../lib/util/got');
 
 let res1 = fs.readFileSync(
   'test/datasource/cdnjs/_fixtures/d3-force.json',
@@ -17,55 +18,36 @@ let res2 = fs.readFileSync(
 );
 res2 = JSON.parse(res2);
 
-jest.mock('../../lib/util/got');
-
 describe('datasource/cdnjs', () => {
   describe('getPkgReleases', () => {
     beforeEach(() => {
       jest.clearAllMocks();
-      global.repoCache = {};
       return global.renovateCache.rmAll();
     });
     it('returns null for empty result', async () => {
-      got.mockReturnValueOnce(null);
+      got.mockResolvedValueOnce(null);
       expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull();
     });
     it('returns null for missing fields', async () => {
-      got.mockReturnValueOnce({});
+      got.mockResolvedValueOnce({});
       expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull();
     });
     it('returns null for 404', async () => {
-      got.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 404,
-        })
-      );
+      got.mockRejectedValueOnce({ statusCode: 404 });
       expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull();
     });
     it('returns null for 401', async () => {
-      got.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 401,
-        })
-      );
+      got.mockRejectedValueOnce({ statusCode: 401 });
       expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull();
     });
     it('throws for 429', async () => {
-      got.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 429,
-        })
-      );
+      got.mockRejectedValueOnce({ statusCode: 429 });
       await expect(
         getPkgReleases({ lookupName: 'foo/bar' })
       ).rejects.toThrowError(DATASOURCE_FAILURE);
     });
     it('throws for 5xx', async () => {
-      got.mockImplementationOnce(() =>
-        Promise.reject({
-          statusCode: 502,
-        })
-      );
+      got.mockRejectedValueOnce({ statusCode: 502 });
       await expect(
         getPkgReleases({ lookupName: 'foo/bar' })
       ).rejects.toThrowError(DATASOURCE_FAILURE);
@@ -77,21 +59,17 @@ describe('datasource/cdnjs', () => {
       expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull();
     });
     it('returns null with wrong auth token', async () => {
-      got.mockReturnValueOnce(
-        Promise.reject({
-          statusCode: 401,
-        })
-      );
+      got.mockRejectedValueOnce({ statusCode: 401 });
       const res = await getPkgReleases({ lookupName: 'foo/bar' });
       expect(res).toBeNull();
     });
     it('processes real data', async () => {
-      got.mockReturnValueOnce({ body: res1 });
+      got.mockResolvedValueOnce({ body: res1 });
       const res = await getPkgReleases({ lookupName: 'd3-force/d3-force.js' });
       expect(res).toMatchSnapshot();
     });
     it('filters releases by asset presence', async () => {
-      got.mockReturnValueOnce({ body: res2 });
+      got.mockResolvedValueOnce({ body: res2 });
       const res = await getPkgReleases({
         lookupName: 'bulma/only/0.7.5/style.css',
       });
diff --git a/test/datasource/cdnjs/_fixtures/bulma.json b/test/datasource/cdnjs/_fixtures/bulma.json
index b972a5fe3c..e1a4db5ecd 100644
--- a/test/datasource/cdnjs/_fixtures/bulma.json
+++ b/test/datasource/cdnjs/_fixtures/bulma.json
@@ -1,26 +1,9 @@
 {
-  "name": "bulma",
-  "filename": "css/bulma.min.css",
-  "version": "0.8.0",
-  "description": "Modern CSS framework based on Flexbox",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/jgthms/bulma.git"
   },
-  "keywords": [
-    "css",
-    "sass",
-    "flexbox",
-    "responsive",
-    "framework"
-  ],
-  "author": "bulma",
   "homepage": "http://bulma.io",
-  "autoupdate": {
-    "source": "npm",
-    "target": "bulma"
-  },
-  "license": "MIT",
   "assets": [
     {
       "version": "0.8.0",
diff --git a/test/datasource/cdnjs/_fixtures/d3-force.json b/test/datasource/cdnjs/_fixtures/d3-force.json
index 8d4ffd6ed6..a462418acd 100644
--- a/test/datasource/cdnjs/_fixtures/d3-force.json
+++ b/test/datasource/cdnjs/_fixtures/d3-force.json
@@ -1,29 +1,9 @@
 {
-  "name": "d3-force",
-  "filename": "d3-force.min.js",
-  "version": "1.1.0",
-  "description": "Force-directed graph layout using velocity Verlet integration.",
   "repository": {
     "type": "git",
     "url": "https://github.com/d3/d3-force.git"
   },
-  "keywords": [
-    "d3",
-    "d3-module",
-    "layout",
-    "network",
-    "graph",
-    "force",
-    "verlet",
-    "infovis"
-  ],
-  "author": "d3-force",
   "homepage": "https://d3js.org/d3-force/",
-  "autoupdate": {
-    "source": "npm",
-    "target": "d3-force"
-  },
-  "license": "BSD-3-Clause",
   "assets": [
     {
       "version": "1.1.0",
-- 
GitLab