Working tests!

This commit is contained in:
dangered wolf 2022-08-08 17:24:14 -04:00
parent 790bcdbd78
commit 1810d885c4
No known key found for this signature in database
GPG key ID: 41E4D37680ED8B58
9 changed files with 4946 additions and 259 deletions

19
jestconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"testEnvironment": "miniflare",
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"globals": {
"BRANDING_NAME": "FixTweet",
"BRANDING_NAME_DISCORD": "FixTweetBrandingDiscord",
"DIRECT_MEDIA_DOMAINS": "d.fxtwitter.com,dl.fxtwitter.com",
"MOSAIC_DOMAIN_LIST": "mosaic.fxtwitter.com",
"API_HOST": "api.fxtwitter.com",
"HOST_URL": "https://fxtwitter.com",
"REDIRECT_URL": "https://github.com/dangeredwolf/FixTweet",
"RELEASE_NAME": "fixtweet-test",
"TEST": true
},
"testRegex": "/test/.*\\.test\\.ts$",
"collectCoverageFrom": ["src/**/*.{ts,js}"]
}

5073
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,8 @@
"log": "wrangler tail",
"reload": "wrangler publish && wrangler tail",
"prettier": "prettier --write .",
"lint:eslint": "eslint --max-warnings=0 src"
"lint:eslint": "eslint --max-warnings=0 src",
"test": "jest --config jestconfig.json --verbose"
},
"author": "dangered wolf",
"license": "MIT",
@ -17,7 +18,7 @@
"@cloudflare/workers-types": "^3.14.1",
"@microsoft/eslint-formatter-sarif": "^3.0.0",
"@sentry/webpack-plugin": "^1.19.0",
"@types/service-worker-mock": "^2.0.1",
"@types/jest": "^28.1.6",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"dotenv": "^16.0.1",
@ -26,8 +27,10 @@
"eslint-config-typescript": "^3.0.0",
"eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-sonarjs": "^0.15.0",
"jest": "^28.1.3",
"jest-environment-miniflare": "^2.6.0",
"prettier": "^2.7.1",
"service-worker-mock": "^2.0.5",
"ts-jest": "^28.0.7",
"ts-loader": "^9.3.1",
"typescript": "^4.7.4",
"webpack": "^5.74.0",

View file

@ -63,3 +63,8 @@ Allow: /watch?v=dQw4w9WgXcQ
Disallow: /doing-harm-to-others
Disallow: /taking-over-the-world`
};
if (typeof TEST !== 'undefined') {
/* Undici gets angry about unicode headers, this is a workaround. */
Constants.RESPONSE_HEADERS['x-powered-by'] = 'Trans Rights';
}

2
src/env.d.ts vendored
View file

@ -8,3 +8,5 @@ declare const API_HOST: string;
declare const SENTRY_DSN: string;
declare const RELEASE_NAME: string;
declare const TEST: boolean | undefined;

View file

@ -140,13 +140,15 @@ router.get('*', async (request: Request) => {
const url = new URL(request.url);
if (url.hostname === Constants.API_HOST) {
return Response.redirect(Constants.API_DOCS_URL, 307);
return Response.redirect(Constants.API_DOCS_URL, 302);
}
return Response.redirect(Constants.REDIRECT_URL, 307);
return Response.redirect(Constants.REDIRECT_URL, 302);
});
const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
const { request } = event;
export const cacheWrapper = async (
request: Request,
event?: FetchEvent
): Promise<Response> => {
const userAgent = request.headers.get('User-Agent') || '';
// https://developers.cloudflare.com/workers/examples/cache-api/
const cacheUrl = new URL(
@ -177,6 +179,7 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
switch (request.method) {
case 'GET':
if (cacheUrl.hostname !== Constants.API_HOST) {
/* cache may be undefined in tests */
const cachedResponse = await cache.match(cacheKey);
if (cachedResponse) {
@ -188,12 +191,12 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
}
// eslint-disable-next-line no-case-declarations
const response = await router.handle(event.request, event);
const response = await router.handle(request, event);
// Store the fetched response as cacheKey
// Use waitUntil so you can return the response without blocking on
// writing to cache
event.waitUntil(cache.put(cacheKey, response.clone()));
event && event.waitUntil(cache.put(cacheKey, response.clone()));
return response;
/* Telegram sends this from Webpage Bot, and Cloudflare sends it if we purge cache, and we respect it.
@ -219,10 +222,10 @@ const cacheWrapper = async (event: FetchEvent): Promise<Response> => {
}
};
const sentryWrapper = async (event: FetchEvent): Promise<void> => {
const sentryWrapper = async (event: FetchEvent, test = false): Promise<void> => {
let sentry: null | Toucan = null;
if (typeof SENTRY_DSN !== 'undefined') {
if (typeof SENTRY_DSN !== 'undefined' && !test) {
sentry = new Toucan({
dsn: SENTRY_DSN,
context: event, // Includes 'waitUntil', which is essential for Sentry logs to be delivered. Also includes 'request' -- no need to set it separately.
@ -239,7 +242,7 @@ const sentryWrapper = async (event: FetchEvent): Promise<void> => {
event.respondWith(
(async (): Promise<Response> => {
try {
return await cacheWrapper(event);
return await cacheWrapper(event.request, event);
} catch (err: unknown) {
sentry && sentry.captureException(err);
@ -256,9 +259,10 @@ const sentryWrapper = async (event: FetchEvent): Promise<void> => {
);
};
/*
Event to receive web requests on Cloudflare Worker
*/
addEventListener('fetch', (event: FetchEvent) => {
sentryWrapper(event);
});
/* May be undefined in test scenarios */
if (typeof addEventListener !== 'undefined') {
/* Event to receive web requests on Cloudflare Worker */
addEventListener('fetch', (event: FetchEvent) => {
sentryWrapper(event);
});
}

View file

@ -196,8 +196,8 @@ export const handleStatus = async (
tweet.poll.choices.forEach(choice => {
// render bar
const bar = '█'.repeat((choice.percentage / 100) * barLength);
str += `${bar}
${choice.label}  (${choice.percentage}%)
// eslint-disable-next-line no-irregular-whitespace
str += `${bar}\n${choice.label}  (${choice.percentage}%)
`;
});

57
test/index.test.ts Normal file
View file

@ -0,0 +1,57 @@
import { cacheWrapper } from '../src/server';
// import { webcrypto } from 'node:crypto';
// const crypto = webcrypto;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// declare const global: any;
const botHeaders = { 'User-Agent': 'Discordbot/2.0' };
const humanHeaders = {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
};
describe('handle', () => {
test('Home page redirect', async () => {
const result = await cacheWrapper(
new Request('https://fxtwitter.com', { method: 'GET' })
);
expect(result.status).toEqual(302);
expect(result.headers.get('location')).toEqual(
'https://github.com/dangeredwolf/FixTweet'
);
});
test('API fetch basic Tweet', async () => {
const result = await cacheWrapper(
new Request('https://api.fxtwitter.com/status/20', {
method: 'GET',
headers: botHeaders
})
);
expect(result.status).toEqual(200);
const response = (await result.json()) as APIResponse;
expect(response).toBeTruthy();
expect(response.code).toEqual(200);
expect(response.message).toEqual('OK');
const tweet = response.tweet as APITweet;
expect(tweet).toBeTruthy();
expect(tweet.url).toEqual('https://twitter.com/jack/status/20');
expect(tweet.id).toEqual('20');
expect(tweet.text).toEqual('just setting up my twttr');
expect(tweet.author.screen_name.toLowerCase()).toEqual('jack');
expect(tweet.author.name).toBeTruthy();
expect(tweet.author.avatar_url).toBeTruthy();
expect(tweet.author.banner_url).toBeTruthy();
expect(tweet.author.avatar_color).toBeTruthy();
expect(tweet.replies).toBeGreaterThan(0);
expect(tweet.retweets).toBeGreaterThan(0);
expect(tweet.likes).toBeGreaterThan(0);
expect(tweet.twitter_card).toEqual('tweet');
expect(tweet.created_at).toEqual('Tue Mar 21 20:50:14 +0000 2006');
expect(tweet.created_timestamp).toEqual(1142974214);
expect(tweet.lang).toEqual('en');
expect(tweet.replying_to).toBeNull();
});
});

View file

@ -11,7 +11,7 @@
"allowJs": true,
"sourceMap": true,
"esModuleInterop": true,
"types": ["@cloudflare/workers-types", "@types/service-worker-mock"]
"types": ["@cloudflare/workers-types", "@types/service-worker-mock", "@types/jest"]
},
"include": ["src"],
"exclude": ["node_modules", "dist", "test"]