From 66124370a6baf662e1b8a03d7ba4da52d4f75a94 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Oct 2019 18:49:47 +0200 Subject: [PATCH 1/5] Added json extension to the list of known static files that have to fall back to 404 on nginx --- .github/ISSUE_TEMPLATE/Bug.md | 1 + .github/ISSUE_TEMPLATE/Question_Support.md | 1 + config/docker/nginx.conf | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md index 182df2be..93e61ad4 100644 --- a/.github/ISSUE_TEMPLATE/Bug.md +++ b/.github/ISSUE_TEMPLATE/Bug.md @@ -16,6 +16,7 @@ With that said, please fill in the information requested next. More information #### Shlink web client version * Version: x.y.z +* How do you use shlink-web-client: app.shlink.io|Docker image|self-hosted #### Summary diff --git a/.github/ISSUE_TEMPLATE/Question_Support.md b/.github/ISSUE_TEMPLATE/Question_Support.md index 8e75a1c1..4fdd085c 100644 --- a/.github/ISSUE_TEMPLATE/Question_Support.md +++ b/.github/ISSUE_TEMPLATE/Question_Support.md @@ -16,6 +16,7 @@ With that said, please fill in the information requested next. More information #### Shlink web client version * Version: x.y.z +* How do you use shlink-web-client: app.shlink.io|Docker image|self-hosted #### Summary diff --git a/config/docker/nginx.conf b/config/docker/nginx.conf index f9b2b816..ddc24af8 100644 --- a/config/docker/nginx.conf +++ b/config/docker/nginx.conf @@ -5,7 +5,7 @@ server { index index.html; # When requesting static paths with extension, try them, and return a 404 if not found - location ~ .+\.(css|js|html|png|jpg|jpeg|gif|bmp|ico|csv|otf|eot|svg|svgz|ttf|woff|woff2|ijmap|pdf|tif|map) { + location ~ .+\.(css|js|html|png|jpg|jpeg|gif|bmp|ico|json|csv|otf|eot|svg|svgz|ttf|woff|woff2|ijmap|pdf|tif|map) { try_files $uri $uri/ =404; } From f74d135922913e2c4a94ba9cb08859a8c6f2ced0 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Oct 2019 19:26:09 +0200 Subject: [PATCH 2/5] Ensured default servers is validated as JSON and ignored otherwise --- src/servers/reducers/server.js | 5 ++- test/servers/reducers/server.test.js | 59 ++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js index f1624027..b14cd713 100644 --- a/src/servers/reducers/server.js +++ b/src/servers/reducers/server.js @@ -31,9 +31,10 @@ export const listServers = ({ listServers, createServers }, { get }) => () => as } // If local list is empty, try to fetch it remotely and calculate IDs for every server + // It's important to parse the content to json, so that it is ignored for other formats (because it will catch) + const getDataAsJsonWithIds = pipe(prop('data'), JSON.parse, map(assocId)); const remoteList = await get(`${homepage}/servers.json`) - .then(prop('data')) - .then(map(assocId)) + .then(getDataAsJsonWithIds) .catch(() => []); createServers(remoteList); diff --git a/test/servers/reducers/server.test.js b/test/servers/reducers/server.test.js index 14d204bb..e47c57b7 100644 --- a/test/servers/reducers/server.test.js +++ b/test/servers/reducers/server.test.js @@ -1,4 +1,5 @@ import { values } from 'ramda'; +import each from 'jest-each'; import reducer, { createServer, deleteServer, @@ -20,27 +21,18 @@ describe('serverReducer', () => { createServers: jest.fn(), }; + afterEach(jest.clearAllMocks); + describe('reducer', () => { it('returns servers when action is FETCH_SERVERS', () => expect(reducer({}, { type: FETCH_SERVERS, list })).toEqual({ loading: false, list })); }); describe('action creators', () => { - beforeEach(() => { - ServersServiceMock.listServers.mockClear(); - ServersServiceMock.createServer.mockReset(); - ServersServiceMock.deleteServer.mockReset(); - ServersServiceMock.createServers.mockReset(); - }); - describe('listServers', () => { - const axios = { get: jest.fn().mockResolvedValue({ data: [] }) }; + const axios = { get: jest.fn() }; const dispatch = jest.fn(); - - beforeEach(() => { - axios.get.mockClear(); - dispatch.mockReset(); - }); + const NoListServersServiceMock = { ...ServersServiceMock, listServers: jest.fn(() => ({})) }; it('fetches servers from local storage when found', async () => { await listServers(ServersServiceMock, axios)()(dispatch); @@ -55,14 +47,49 @@ describe('serverReducer', () => { expect(axios.get).not.toHaveBeenCalled(); }); - it('tries to fetch servers from remote when not found locally', async () => { - const NoListServersServiceMock = { ...ServersServiceMock, listServers: jest.fn(() => ({})) }; + each([ + [ + Promise.resolve({ + data: `[ + { + "id": "111", + "name": "acel.me from servers.json", + "url": "https://acel.me", + "apiKey": "07fb8a96-8059-4094-a24c-80a7d5e7e9b0" + }, + { + "id": "222", + "name": "Local from servers.json", + "url": "http://localhost:8000", + "apiKey": "7a531c75-134e-4d5c-86e0-a71b7167b57a" + } +]`, + }), + { + 111: { + id: '111', + name: 'acel.me from servers.json', + url: 'https://acel.me', + apiKey: '07fb8a96-8059-4094-a24c-80a7d5e7e9b0', + }, + 222: { + id: '222', + name: 'Local from servers.json', + url: 'http://localhost:8000', + apiKey: '7a531c75-134e-4d5c-86e0-a71b7167b57a', + }, + }, + ], + [ Promise.resolve(''), {}], + [ Promise.reject({}), {}], + ]).it('tries to fetch servers from remote when not found locally', async (mockedValue, expectedList) => { + axios.get.mockReturnValue(mockedValue); await listServers(NoListServersServiceMock, axios)()(dispatch); expect(dispatch).toHaveBeenCalledTimes(2); expect(dispatch).toHaveBeenNthCalledWith(1, { type: FETCH_SERVERS_START }); - expect(dispatch).toHaveBeenNthCalledWith(2, { type: FETCH_SERVERS, list: {} }); + expect(dispatch).toHaveBeenNthCalledWith(2, { type: FETCH_SERVERS, list: expectedList }); expect(NoListServersServiceMock.listServers).toHaveBeenCalledTimes(1); expect(NoListServersServiceMock.createServer).not.toHaveBeenCalled(); expect(NoListServersServiceMock.deleteServer).not.toHaveBeenCalled(); From fc7a2e0c6dba501735bc3db4600782beba806495 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Oct 2019 19:38:32 +0200 Subject: [PATCH 3/5] Ensured response from servers.json has been parsed to a json array --- src/servers/reducers/server.js | 14 ++++++++++++-- test/servers/reducers/server.test.js | 28 ++++++++++++++-------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js index b14cd713..e28e050a 100644 --- a/src/servers/reducers/server.js +++ b/src/servers/reducers/server.js @@ -32,9 +32,19 @@ export const listServers = ({ listServers, createServers }, { get }) => () => as // If local list is empty, try to fetch it remotely and calculate IDs for every server // It's important to parse the content to json, so that it is ignored for other formats (because it will catch) - const getDataAsJsonWithIds = pipe(prop('data'), JSON.parse, map(assocId)); + const getDataAsArrayWithIds = pipe( + prop('data'), + (value) => { + if (!Array.isArray(value)) { + throw new Error('Value is not an array'); + } + + return value; + }, + map(assocId), + ); const remoteList = await get(`${homepage}/servers.json`) - .then(getDataAsJsonWithIds) + .then(getDataAsArrayWithIds) .catch(() => []); createServers(remoteList); diff --git a/test/servers/reducers/server.test.js b/test/servers/reducers/server.test.js index e47c57b7..278e3e92 100644 --- a/test/servers/reducers/server.test.js +++ b/test/servers/reducers/server.test.js @@ -50,20 +50,20 @@ describe('serverReducer', () => { each([ [ Promise.resolve({ - data: `[ - { - "id": "111", - "name": "acel.me from servers.json", - "url": "https://acel.me", - "apiKey": "07fb8a96-8059-4094-a24c-80a7d5e7e9b0" - }, - { - "id": "222", - "name": "Local from servers.json", - "url": "http://localhost:8000", - "apiKey": "7a531c75-134e-4d5c-86e0-a71b7167b57a" - } -]`, + data: [ + { + id: '111', + name: 'acel.me from servers.json', + url: 'https://acel.me', + apiKey: '07fb8a96-8059-4094-a24c-80a7d5e7e9b0', + }, + { + id: '222', + name: 'Local from servers.json', + url: 'http://localhost:8000', + apiKey: '7a531c75-134e-4d5c-86e0-a71b7167b57a', + }, + ], }), { 111: { From 86cce5b205dec17bdb0cc2806759b17c93679bc9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Oct 2019 19:39:59 +0200 Subject: [PATCH 4/5] Updated changelog --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c99e2e9..38f6dddf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org). +## 2.2.2 - 2019-10-21 + +#### Added + +* *Nothing* + +#### Changed + +* *Nothing* + +#### Deprecated + +* *Nothing* + +#### Removed + +* *Nothing* + +#### Fixed + +* [#167](https://github.com/shlinkio/shlink-web-client/issues/167) Fixed `/servers.json` path not being ignored when returning something other than an array. + + ## 2.2.1 - 2019-10-18 #### Added From 0237af771d5c0ced2f3022c4b3699763a8408bb9 Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Mon, 21 Oct 2019 19:45:35 +0200 Subject: [PATCH 5/5] Fixed outdated comment --- src/servers/reducers/server.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/servers/reducers/server.js b/src/servers/reducers/server.js index e28e050a..576eca14 100644 --- a/src/servers/reducers/server.js +++ b/src/servers/reducers/server.js @@ -30,8 +30,7 @@ export const listServers = ({ listServers, createServers }, { get }) => () => as return; } - // If local list is empty, try to fetch it remotely and calculate IDs for every server - // It's important to parse the content to json, so that it is ignored for other formats (because it will catch) + // If local list is empty, try to fetch it remotely (making sure it's an array) and calculate IDs for every server const getDataAsArrayWithIds = pipe( prop('data'), (value) => {