Skip to content

Commit

Permalink
fix: Transform of URL search params' cases (#690)
Browse files Browse the repository at this point in the history
  • Loading branch information
neet committed Nov 20, 2022
1 parent 95f50b7 commit a9582ff
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 33 deletions.
10 changes: 3 additions & 7 deletions src/http/base-http.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type { MastoConfig } from '../config';
import type { MimeType } from '../serializers';
import { railsQueryString } from '../serializers/rails-querystring';
import type { MimeType, Serializer } from '../serializers';
import type { Data, Headers, Http, Request, Response } from './http';

export abstract class BaseHttp implements Http {
abstract readonly config: MastoConfig;
abstract readonly serializer: Serializer;

abstract request<T>(request: Request): Promise<Response<T>>;

Expand All @@ -19,12 +19,8 @@ export abstract class BaseHttp implements Http {
return headers;
}

encodeParams(params: Data = {}): string {
return railsQueryString.stringify(params);
}

resolveUrl(path: string, params: Data = {}): string {
const searchParams = this.encodeParams(params);
const searchParams = this.serializer.serializeQueryString(params);

return `${this.config.url}${path}${
searchParams !== '' ? `?${searchParams}` : ''
Expand Down
2 changes: 1 addition & 1 deletion src/http/http-axios-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export class HttpAxiosImpl extends BaseHttp implements Http {
return this.serializer.deserialize(contentType, data);
},
paramsSerializer: {
serialize: (params) => this.encodeParams(params),
serialize: (params) => this.serializer.serializeQueryString(params),
},
});
}
Expand Down
9 changes: 6 additions & 3 deletions src/serializers/serializer-native-impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { camelCase, snakeCase } from 'change-case';

import { flattenObject } from './form-data';
import { railsQueryString } from './rails-querystring';
import type { MimeType, Serializer } from './serializer';
import { transformKeys } from './transform-keys';

Expand All @@ -21,15 +22,17 @@ export class SerializerNativeImpl implements Serializer {
.entries(flattenObject(data))) formData.append(key, value as string);
return formData;
}
case 'application/x-www-form-urlencoded': {
return new URLSearchParams(Object.entries(data as Record<string, string>)).toString();
}
default: {
return;
}
}
}

serializeQueryString(rawData: unknown): string {
const data = transformKeys(rawData, snakeCase);
return railsQueryString.stringify(data);
}

deserialize<T = Record<string, unknown>>(type: MimeType, data: string): T {
switch (type) {
case 'application/json': {
Expand Down
9 changes: 6 additions & 3 deletions src/serializers/serializer-nodejs-impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { camelCase, snakeCase } from 'change-case';

import { flattenObject } from './form-data';
import { railsQueryString } from './rails-querystring';
import type { MimeType, Serializer } from './serializer';
import { transformKeys } from './transform-keys';

Expand All @@ -21,15 +22,17 @@ export class SerializerNodejsImpl implements Serializer {
.entries(flattenObject(data))) formData.append(key, value as string);
return formData;
}
case 'application/x-www-form-urlencoded': {
return new URLSearchParams(data as Record<string, string>).toString();
}
default: {
return;
}
}
}

serializeQueryString(rawData: unknown): string {
const data = transformKeys(rawData, snakeCase);
return railsQueryString.stringify(data);
}

deserialize<T = Record<string, unknown>>(type: MimeType, data: string): T {
switch (type) {
case 'application/json': {
Expand Down
6 changes: 2 additions & 4 deletions src/serializers/serializer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
export type MimeType =
| 'application/json'
| 'multipart/form-data'
| 'application/x-www-form-urlencoded';
export type MimeType = 'application/json' | 'multipart/form-data';

export interface Serializer {
serialize(type: MimeType, data: unknown): unknown;
serializeQueryString(data: unknown): string;
deserialize<T = Record<string, unknown>>(type: MimeType, data: unknown): T;
}
51 changes: 36 additions & 15 deletions tests/accounts.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import assert from 'node:assert';

import type { MastoClient } from '../src/clients';
import { login } from '../test-utils/login';

describe('account', () => {
let client: MastoClient;
const TARGET_ID = process.env.TEST_TARGET_ID ?? '200896';

beforeAll(async () => {
client = await login();
Expand All @@ -27,45 +30,63 @@ describe('account', () => {
expect(me.id).toBe(someone.id);
});

it('can follow / unfollow by ID', async () => {
let relationship = await client.accounts.follow('200896');
it('follows / unfollow by ID', async () => {
let relationship = await client.accounts.follow(TARGET_ID);
expect(relationship.following).toBe(true);

relationship = await client.accounts.unfollow('200896');
relationship = await client.accounts.unfollow(TARGET_ID);
expect(relationship.following).toBe(false);
});

it('can block / unblock by ID', async () => {
let relationship = await client.accounts.block('200896');
it('blocks / unblock by ID', async () => {
let relationship = await client.accounts.block(TARGET_ID);
expect(relationship.blocking).toBe(true);

relationship = await client.accounts.unblock('200896');
relationship = await client.accounts.unblock(TARGET_ID);
expect(relationship.blocking).toBe(false);
});

// it('can pin / unpin by ID', async () => {
// await client.accounts.follow('200896');
// let relationship = await client.accounts.pin('200896');
// await client.accounts.follow(TARGET_ID);
// let relationship = await client.accounts.pin(TARGET_ID);
// expect(relationship.endorsed).toBe(true);

// relationship = await client.accounts.unpin('200896');
// await client.accounts.unfollow('200896');
// relationship = await client.accounts.unpin(TARGET_ID);
// await client.accounts.unfollow(TARGET_ID);
// expect(relationship.endorsed).toBe(false);
// });

it('can mute / unmute by ID', async () => {
let relationship = await client.accounts.mute('200896');
it('mutes / unmute by ID', async () => {
let relationship = await client.accounts.mute(TARGET_ID);
expect(relationship.muting).toBe(true);

relationship = await client.accounts.unmute('200896');
relationship = await client.accounts.unmute(TARGET_ID);
expect(relationship.muting).toBe(false);
});

it('can create a note', async () => {
it('creates a note', async () => {
const comment = Math.random().toString();
const relationship = await client.accounts.createNote('200896', {
const relationship = await client.accounts.createNote(TARGET_ID, {
comment,
});
expect(relationship.note).toBe(comment);
});

it('excludes replies from getStatusesIterable', async () => {
const statuses = await client.accounts
.getStatusesIterable(TARGET_ID, {
excludeReplies: true,
})
.next();
assert(!statuses.done);

expect(
statuses.value
// `excludeReplies` won't exclude reblogs
.filter((status) => !status.reblog)
// `excludeReplies` won't exclude self-replies
.filter((status) => status.inReplyToAccountId !== status.account.id)
.every((status) => status.inReplyToId == undefined),
).toBe(true);
});
});

0 comments on commit a9582ff

Please sign in to comment.