feat: link official news to Ubisoft
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
extractNimbusApiKey,
|
||||
fetchUbisoftNewsThumbnail,
|
||||
fetchUbisoftNews,
|
||||
UbisoftNewsSourceError,
|
||||
} from './ubisoft-news-client';
|
||||
@@ -29,6 +30,10 @@ function item(id: string, overrides: Record<string, unknown> = {}) {
|
||||
abstract: 'Read the latest official update.',
|
||||
date: '2024-03-11T15:20:00.000Z',
|
||||
button: { buttonUrl: `/${id}/division-resurgence-update` },
|
||||
thumbnail: {
|
||||
url: `https://staticctf.ubisoft.com/news/${id}/cover.jpg`,
|
||||
description: 'Division Resurgence key art',
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@@ -68,6 +73,10 @@ describe('Ubisoft News client', () => {
|
||||
expect(result[0].canonicalUrl).toBe(
|
||||
`https://www.ubisoft.com/en-us/game/the-division/resurgence/news-updates/${externalIdOne}/earlier-update`,
|
||||
);
|
||||
expect(result[0].thumbnailUrl).toBe(
|
||||
`https://staticctf.ubisoft.com/news/${externalIdOne}/cover.jpg`,
|
||||
);
|
||||
expect(result[0].thumbnailAlt).toBe('Division Resurgence key art');
|
||||
expect(fetchImpl).toHaveBeenCalledTimes(3);
|
||||
const secondRequest = fetchImpl.mock.calls[1];
|
||||
expect(String(secondRequest[0])).toContain('locale=en-us');
|
||||
@@ -109,6 +118,26 @@ describe('Ubisoft News client', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects thumbnails outside Ubisoft image hosting', async () => {
|
||||
const fetchImpl = vi
|
||||
.fn<typeof fetch>()
|
||||
.mockResolvedValueOnce(landing())
|
||||
.mockResolvedValueOnce(
|
||||
page(1, [
|
||||
item(externalIdOne, {
|
||||
thumbnail: {
|
||||
url: 'https://example.com/cover.jpg',
|
||||
description: 'Untrusted image',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
await expect(fetchUbisoftNews(fetchImpl)).rejects.toThrow(
|
||||
'Ubisoft news thumbnail was outside the approved image host.',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not follow provider redirects', async () => {
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
|
||||
new Response(null, {
|
||||
@@ -122,4 +151,54 @@ describe('Ubisoft News client', () => {
|
||||
);
|
||||
expect(fetchImpl.mock.calls[0][1]?.redirect).toBe('error');
|
||||
});
|
||||
|
||||
it('fetches bounded raster thumbnails without following redirects', async () => {
|
||||
const bytes = new Uint8Array([1, 2, 3]);
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
|
||||
new Response(bytes, {
|
||||
headers: { 'content-type': 'image/jpeg; charset=binary' },
|
||||
}),
|
||||
);
|
||||
|
||||
const image = await fetchUbisoftNewsThumbnail(
|
||||
'https://staticctf.ubisoft.com/news/cover.jpg',
|
||||
fetchImpl,
|
||||
);
|
||||
expect(image.contentType).toBe('image/jpeg');
|
||||
expect(image.body).toEqual(bytes);
|
||||
expect(fetchImpl.mock.calls[0][1]?.redirect).toBe('error');
|
||||
});
|
||||
|
||||
it('rejects thumbnail responses that are not raster images', async () => {
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
|
||||
new Response('<svg/>', {
|
||||
headers: { 'content-type': 'image/svg+xml' },
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
fetchUbisoftNewsThumbnail(
|
||||
'https://staticctf.ubisoft.com/news/cover.svg',
|
||||
fetchImpl,
|
||||
),
|
||||
).rejects.toThrow('not a supported image');
|
||||
});
|
||||
|
||||
it('rejects thumbnails larger than the response bound', async () => {
|
||||
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValueOnce(
|
||||
new Response(new Uint8Array([1]), {
|
||||
headers: {
|
||||
'content-type': 'image/jpeg',
|
||||
'content-length': String(5 * 1024 * 1024 + 1),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await expect(
|
||||
fetchUbisoftNewsThumbnail(
|
||||
'https://staticctf.ubisoft.com/news/large-cover.jpg',
|
||||
fetchImpl,
|
||||
),
|
||||
).rejects.toThrow('exceeded the size limit');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user