Skip to content

Commit

Permalink
chore(tests): Add tests for lists, notifications, etc
Browse files Browse the repository at this point in the history
improve status tests

improve account tests

add tests

Add missing search test
  • Loading branch information
neet committed Apr 8, 2023
1 parent 436b72e commit 8494316
Show file tree
Hide file tree
Showing 22 changed files with 368 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ jobs:
-p 3000:3000
-p 4000:4000
--name ${{ env.MASTODON_CONTAINER }}
-e DEEPL_PLAN=${{ secrets.DEEPL_PLAN }}
-e DEEPL_API_KEY=${{ secrets.DEEPL_API_KEY }}
--env-file ./.github/.env.test
docker.io/neetshin/mastodon-dev:latest
bash -c "foreman start"
Expand Down
1 change: 1 addition & 0 deletions src/http/get-content-type.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ test.each([
[{ 'Content-Type': 'text/plain; charset=utf-8' }, 'text/plain'],
[{ 'content-type': 'text/plain; charset=utf-8' }, 'text/plain'],
[{ 'Content-Type': 'text/plain' }, 'text/plain'],
[{}, undefined],
])('removes charset from content-type', (headers, expected) => {
expect(getContentType(new Headers(headers))).toBe(expected);
});
131 changes: 131 additions & 0 deletions src/logger/base-logger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* eslint-disable unicorn/no-useless-undefined */
import { LogLevel } from './log-level';
import { log, LoggerMockImpl } from './logger-mock-impl';

describe('debug', () => {
const level = LogLevel.from('debug');

afterEach(() => {
log.mockClear();
});

test('debug', () => {
const logger = new LoggerMockImpl(level);
logger.debug('message');
expect(log).toBeCalledWith('debug', 'message', undefined);
});

test('info', () => {
const logger = new LoggerMockImpl(level);
logger.info('message');
expect(log).toBeCalledWith('info', 'message', undefined);
});

test('warn', () => {
const logger = new LoggerMockImpl(level);
logger.warn('message');
expect(log).toBeCalledWith('warn', 'message', undefined);
});

test('error', () => {
const logger = new LoggerMockImpl(level);
logger.error('message');
expect(log).toBeCalledWith('error', 'message', undefined);
});
});

describe('info', () => {
const level = LogLevel.from('info');

afterEach(() => {
log.mockClear();
});

test('debug', () => {
const logger = new LoggerMockImpl(level);
logger.debug('message');
expect(log).not.toBeCalledWith('debug', 'message', undefined);
});

test('info', () => {
const logger = new LoggerMockImpl(level);
logger.info('message');
expect(log).toBeCalledWith('info', 'message', undefined);
});

test('warn', () => {
const logger = new LoggerMockImpl(level);
logger.warn('message');
expect(log).toBeCalledWith('warn', 'message', undefined);
});

test('error', () => {
const logger = new LoggerMockImpl(level);
logger.error('message');
expect(log).toBeCalledWith('error', 'message', undefined);
});
});

describe('warn', () => {
const level = LogLevel.from('warn');

afterEach(() => {
log.mockClear();
});

test('debug', () => {
const logger = new LoggerMockImpl(level);
logger.debug('message');
expect(log).not.toBeCalledWith('debug', 'message', undefined);
});

test('info', () => {
const logger = new LoggerMockImpl(level);
logger.info('message');
expect(log).not.toBeCalledWith('info', 'message', undefined);
});

test('warn', () => {
const logger = new LoggerMockImpl(level);
logger.warn('message');
expect(log).toBeCalledWith('warn', 'message', undefined);
});

test('error', () => {
const logger = new LoggerMockImpl(level);
logger.error('message');
expect(log).toBeCalledWith('error', 'message', undefined);
});
});

describe('error', () => {
const level = LogLevel.from('error');

afterEach(() => {
log.mockClear();
});

test('debug', () => {
const logger = new LoggerMockImpl(level);
logger.debug('message');
expect(log).not.toBeCalledWith('debug', 'message', undefined);
});

test('info', () => {
const logger = new LoggerMockImpl(level);
logger.info('message');
expect(log).not.toBeCalledWith('info', 'message', undefined);
});

test('warn', () => {
const logger = new LoggerMockImpl(level);
logger.warn('message');
expect(log).not.toBeCalledWith('warn', 'message', undefined);
});

test('error', () => {
const logger = new LoggerMockImpl(level);
logger.error('message');
expect(log).toBeCalledWith('error', 'message', undefined);
});
});
8 changes: 4 additions & 4 deletions src/logger/base-logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,25 @@ export abstract class BaseLogger {

abstract log(type: LogType, message: string, meta: unknown): void;

debug(message: string, meta: unknown): void {
debug(message: string, meta?: unknown): void {
if (this.logLevel.satisfies('debug')) {
this.log('debug', message, meta);
}
}

info(message: string, meta: unknown): void {
info(message: string, meta?: unknown): void {
if (this.logLevel.satisfies('info')) {
this.log('info', message, meta);
}
}

warn(message: string, meta: unknown): void {
warn(message: string, meta?: unknown): void {
if (this.logLevel.satisfies('warn')) {
this.log('warn', message, meta);
}
}

error(message: string, meta: unknown): void {
error(message: string, meta?: unknown): void {
if (this.logLevel.satisfies('error')) {
this.log('error', message, meta);
}
Expand Down
7 changes: 7 additions & 0 deletions src/logger/logger-mock-impl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { BaseLogger } from './base-logger';

export const log = jest.fn();

export class LoggerMockImpl extends BaseLogger {
log = log;
}
3 changes: 3 additions & 0 deletions src/mastodon/v1/aggregate-repository-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ export class AggregateRepositoryAdmin {
readonly trends: AdminRepositories.TrendRepository;

/** @deprecated Use `accounts` instead */
/* istanbul ignore next */
get account(): AdminRepositories.AccountRepository {
return this.accounts;
}

/** @deprecated Use `reports` instead */
/* istanbul ignore next */
get report(): AdminRepositories.ReportRepository {
return this.reports;
}

/** @deprecated Use `emailDomainBlocks` instead */
/* istanbul ignore next */
get domainEmailBlocks(): AdminRepositories.EmailDomainBlockRepository {
return this.emailDomainBlocks;
}
Expand Down
1 change: 1 addition & 0 deletions src/mastodon/v1/aggregate-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export class AggregateRepository {
* @return Results
* @see https://docs.joinmastodon.org/methods/search/
*/
/* istanbul ignore next */
@version({ since: '1.1.0', until: '3.0.0' })
search(params: SearchParams): Paginator<Search, SearchParams> {
return new Paginator(this.http, `/api/v1/search`, params);
Expand Down
2 changes: 1 addition & 1 deletion src/mastodon/v1/entities/translation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export interface Translation {
/** The translated text of the status. */
id: string;
content: string;
/** The language of the source text, as auto-detected by the machine translation provider. */
detectedLanguageSource: string;
/** The service that provided the machine translation. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ export class MediaAttachmentRepository
*/
@deprecated('Use MastoClient.v2.media.create instead')
@version({ since: '0.0.0', until: '3.1.3' })
/* istanbul ignore next */
create(params: CreateMediaAttachmentParams): Promise<MediaAttachment> {
/* istanbul-ignore-next */
return this.http.post<MediaAttachment>(`/api/v1/media`, params, {
headers: { 'Content-Type': 'multipart/form-data' },
});
Expand Down
1 change: 1 addition & 0 deletions src/mastodon/v1/repositories/status-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ export class StatusRepository implements Repository<Status> {
*/
@deprecated('Use `card` attribute of status instead')
@version({ since: '0.0.0', until: '2.9.3' })
/* istanbul ignore next */
fetchCard(id: string): Promise<PreviewCard> {
return this.http.get(`/api/v1/statuses/${id}/card`);
}
Expand Down
1 change: 1 addition & 0 deletions src/mastodon/v1/repositories/suggestion-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export class SuggestionRepository
* @see https://docs.joinmastodon.org/methods/accounts/suggestions/
*/
@version({ since: '2.4.3' })
/* istanbul ignore next */
remove(id: string): Promise<void> {
return this.http.delete(`/api/v1/suggestions/${id}`);
}
Expand Down
1 change: 1 addition & 0 deletions src/mastodon/v1/repositories/timeline-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export class TimelineRepository {
*/
@deprecated('Use conversations API instead')
@version({ since: '0.0.0', until: '2.9.3' })
/* istanbul ignore next */
listDirect(
params?: ListTimelineParams,
): Paginator<Status[], ListTimelineParams> {
Expand Down
37 changes: 37 additions & 0 deletions src/paginator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,23 @@ describe('Paginator', () => {
});
});

it('is AsyncIterable', async () => {
const paginator = new Paginator(http, '/v1/api/timelines', {
foo: 'bar',
});

// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _ of paginator) {
break;
}

expect(http.request).toBeCalledWith({
requestInit: { method: 'GET' },
path: '/v1/api/timelines',
searchParams: { foo: 'bar' },
});
});

it('clones itself', async () => {
const paginator1 = new Paginator(http, '/some/api', { query: 'value' });
const paginator2 = paginator1.clone();
Expand Down Expand Up @@ -124,4 +141,24 @@ describe('Paginator', () => {
searchParams: { types: ['mention'], max_id: '123456' },
});
});

it('is thenable', () => {
const paginator = new Paginator(http, '/v1/api/timelines');
const onFulfilled = jest.fn();
paginator.then(onFulfilled);
expect(onFulfilled).toBeCalledTimes(0);
});

it('is thenable (error)', () => {
const paginator = new Paginator(http, '/v1/api/timelines');
http.request.mockImplementation(() => {
throw new Error('mock error');
});

const onFulfilled = jest.fn();
const onRejected = jest.fn();
paginator.then(onFulfilled, onRejected);
expect(onFulfilled).not.toBeCalled();
expect(onRejected).toBeCalledTimes(0);
});
});
5 changes: 5 additions & 0 deletions src/serializers/form-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ test('nested object', () => {
'e[e2][e22][2][value]': 3,
});
});

test('returns input when it is neither an array nor an object', () => {
const value = flattenObject('hello');
expect(value).toBe('hello');
});
20 changes: 20 additions & 0 deletions src/serializers/serializer-native-impl.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MastoDeserializeError } from '../errors';
import { SerializerNativeImpl } from './serializer-native-impl';

describe('SerializerNativeImpl', () => {
Expand Down Expand Up @@ -36,6 +37,14 @@ describe('SerializerNativeImpl', () => {
);
});

it('encodes an object to null when unknown type passed', () => {
const data = serializer.serialize(
'text/html',
'<html><body>test</body></html>',
);
expect(data).toBeUndefined();
});

it('encodes an object to a querystring', () => {
const data = serializer.serializeQueryString({
keyName: 'value',
Expand All @@ -54,4 +63,15 @@ describe('SerializerNativeImpl', () => {
);
expect(data).toEqual({ keyName: 'value' });
});

it('returns undefined for unparsable JSON', () => {
const data = serializer.deserialize('application/json', '');
expect(data).toBeUndefined();
});

it('throws deserialize error for unknown types', () => {
expect(() => {
serializer.deserialize('text/html', '<html><body>test</body></html>');
}).toThrowError(MastoDeserializeError);
});
});
27 changes: 25 additions & 2 deletions tests/v1/accounts.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('account', () => {
expect(token.accessToken).toEqual(expect.any(String));
});
});

it('verifies credential', () => {
return clients.use(async (alice) => {
const me = await alice.v1.accounts.verifyCredentials();
Expand Down Expand Up @@ -179,7 +180,23 @@ describe('account', () => {
});
});

test.todo('list lists');
it('lists lists', () => {
return clients.use(2, async ([alice, bob]) => {
const bobAccount = await bob.v1.accounts.verifyCredentials();
const list = await alice.v1.lists.create({ title: 'title' });
await alice.v1.accounts.follow(bobAccount.id);

try {
await alice.v1.lists.addAccount(list.id, {
accountIds: [bobAccount.id],
});
const accounts = await alice.v1.accounts.listLists(bobAccount.id);
expect(accounts).toContainId(list.id);
} finally {
await alice.v1.lists.remove(list.id);
}
});
});

it('lists featured tags', () => {
return clients.use(async (client) => {
Expand Down Expand Up @@ -213,7 +230,13 @@ describe('account', () => {
});
});

test.todo('lookup');
it('lookup', () => {
return clients.use(async (client) => {
const me = await client.v1.accounts.verifyCredentials();
const account = await client.v1.accounts.lookup({ acct: me.acct });
expect(account.id).toBe(me.id);
});
});

it('removes from followers', () => {
return clients.use(2, async ([alice, bob]) => {
Expand Down

0 comments on commit 8494316

Please sign in to comment.