Merge pull request #483 from acelaya-forks/feature/import-servers-win

Feature/import servers win
This commit is contained in:
Alejandro Celaya 2021-09-12 10:04:33 +02:00 committed by GitHub
commit 0e47f9b502
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 22 deletions

View file

@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed ### Fixed
* [#478](https://github.com/shlinkio/shlink-web-client/pull/478) Fixed tags including special chars not being properly URL encoded before using them as query params. * [#478](https://github.com/shlinkio/shlink-web-client/pull/478) Fixed tags including special chars not being properly URL encoded before using them as query params.
* [#480](https://github.com/shlinkio/shlink-web-client/pull/480) Fixed servers import on Chromium-based browsers when using windows.
## [3.2.0] - 2021-07-12 ## [3.2.0] - 2021-07-12

View file

@ -1,29 +1,37 @@
import { CsvJson } from 'csvjson'; import { CsvJson } from 'csvjson';
import { ServerData } from '../data'; import { ServerData } from '../data';
interface CsvFile extends File { const validateServer = (server: any): server is ServerData =>
type: 'text/csv' | 'text/comma-separated-values' | 'application/csv'; typeof server.url === 'string' && typeof server.apiKey === 'string' && typeof server.name === 'string';
}
const CSV_MIME_TYPES = [ 'text/csv', 'text/comma-separated-values', 'application/csv' ]; const validateServers = (servers: any): servers is ServerData[] =>
const isCsv = (file?: File | null): file is CsvFile => !!file && CSV_MIME_TYPES.includes(file.type); Array.isArray(servers) && servers.every(validateServer);
export default class ServersImporter { export default class ServersImporter {
public constructor(private readonly csvjson: CsvJson, private readonly fileReaderFactory: () => FileReader) {} public constructor(private readonly csvJson: CsvJson, private readonly fileReaderFactory: () => FileReader) {}
public readonly importServersFromFile = async (file?: File | null): Promise<ServerData[]> => { public readonly importServersFromFile = async (file?: File | null): Promise<ServerData[]> => {
if (!isCsv(file)) { if (!file) {
throw new Error('No file provided or file is not a CSV'); throw new Error('No file provided');
} }
const reader = this.fileReaderFactory(); const reader = this.fileReaderFactory();
return new Promise((resolve) => { return new Promise((resolve, reject) => {
reader.addEventListener('loadend', (e: ProgressEvent<FileReader>) => { reader.addEventListener('loadend', (e: ProgressEvent<FileReader>) => {
try {
// TODO Read as stream, otherwise, if the file is too big, this will block the browser tab
const content = e.target?.result?.toString() ?? ''; const content = e.target?.result?.toString() ?? '';
const servers = this.csvjson.toObject<ServerData>(content); const servers = this.csvJson.toObject(content);
if (!validateServers(servers)) {
throw new Error('Provided file does not have the right format.');
}
resolve(servers); resolve(servers);
} catch (e) {
reject(e);
}
}); });
reader.readAsText(file); reader.readAsText(file);
}); });

View file

@ -21,23 +21,70 @@ describe('ServersImporter', () => {
describe('importServersFromFile', () => { describe('importServersFromFile', () => {
it('rejects with error if no file was provided', async () => { it('rejects with error if no file was provided', async () => {
await expect(importer.importServersFromFile()).rejects.toEqual( await expect(importer.importServersFromFile()).rejects.toEqual(
new Error('No file provided or file is not a CSV'), new Error('No file provided'),
); );
}); });
it('rejects with error if provided file is not a CSV', async () => { it('rejects with error if parsing the file fails', async () => {
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual( const expectedError = new Error('Error parsing file');
new Error('No file provided or file is not a CSV'),
); toObject.mockImplementation(() => {
throw expectedError;
});
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(expectedError);
}); });
it.each([ it.each([
[ 'text/csv' ], [{}],
[ 'text/comma-separated-values' ], [ undefined ],
[ 'application/csv' ], [[{ foo: 'bar' }]],
])('reads file when a CSV is provided', async (type) => { [
await importer.importServersFromFile(Mock.of<File>({ type })); [
{
url: 1,
apiKey: 1,
name: 1,
},
],
],
[
[
{
url: 'foo',
apiKey: 'foo',
name: 'foo',
},
{ bar: 'foo' },
],
],
])('rejects with error if provided file does not parse to valid list of servers', async (parsedObject) => {
toObject.mockReturnValue(parsedObject);
await expect(importer.importServersFromFile(Mock.of<File>({ type: 'text/html' }))).rejects.toEqual(
new Error('Provided file does not have the right format.'),
);
});
it('reads file when a CSV containing valid servers is provided', async () => {
const expectedServers = [
{
url: 'foo',
apiKey: 'foo',
name: 'foo',
},
{
url: 'bar',
apiKey: 'bar',
name: 'bar',
},
];
toObject.mockReturnValue(expectedServers);
const result = await importer.importServersFromFile(Mock.all<File>());
expect(result).toEqual(expectedServers);
expect(readAsText).toHaveBeenCalledTimes(1); expect(readAsText).toHaveBeenCalledTimes(1);
expect(toObject).toHaveBeenCalledTimes(1); expect(toObject).toHaveBeenCalledTimes(1);
}); });