Skip to content

Commit 27bb01e

Browse files
committedAug 14, 2024·
refactor: reimplement case-handling utils in rgbpp-sdk-service, also add tests for the utils
1 parent 8482241 commit 27bb01e

File tree

10 files changed

+496
-1846
lines changed

10 files changed

+496
-1846
lines changed
 

‎.github/workflows/test.yaml

+4-1
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,14 @@ jobs:
4949
- name: Lint packages
5050
run: pnpm run lint
5151

52-
- name: Run tests for packages
52+
- name: Run unit tests for the packages
5353
run: pnpm run test:packages
5454
env:
5555
VITE_CKB_NODE_URL: https://testnet.ckb.dev/rpc
5656
VITE_CKB_INDEXER_URL: https://testnet.ckb.dev/indexer
5757
VITE_BTC_SERVICE_URL: https://btc-assets-api.testnet.mibao.pro
5858
VITE_BTC_SERVICE_TOKEN: ${{ secrets.TESTNET_SERVICE_TOKEN }}
5959
VITE_BTC_SERVICE_ORIGIN: https://btc-assets-api.testnet.mibao.pro
60+
61+
- name: Run unit tests for the rgbpp-sdk-service
62+
run: pnpm run test:service

‎apps/service/package.json

+10-29
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
{
22
"name": "rgbpp-sdk-service",
33
"version": "0.0.1",
4-
"description": "",
5-
"author": "",
64
"private": true,
7-
"license": "UNLICENSED",
85
"type": "module",
96
"scripts": {
107
"build": "nest build",
@@ -15,56 +12,40 @@
1512
"start:debug": "nest start --debug --watch",
1613
"start:prod": "node dist/src/main",
1714
"lint": "eslint \"src/**/*.ts\" --fix",
18-
"test": "jest",
19-
"test:watch": "jest --watch",
20-
"test:cov": "jest --coverage",
21-
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
22-
"test:e2e": "jest --config ./test/jest-e2e.json"
15+
"test": "vitest",
16+
"test:watch": "vitest --watch",
17+
"test:cov": "vitest run --coverage",
18+
"test:debug": "vitest --inspect-brk --inspect --logHeapUsage --threads=false"
2319
},
2420
"dependencies": {
2521
"@nestjs/common": "^10.0.0",
2622
"@nestjs/config": "^3.2.2",
2723
"@nestjs/core": "^10.3.9",
2824
"@nestjs/platform-fastify": "^10.3.9",
29-
"convert-keys": "^1.3.4",
25+
"camelcase-keys": "^7.0.2",
3026
"json-rpc-2.0": "^1.7.0",
3127
"lodash": "^4.17.21",
3228
"reflect-metadata": "^0.2.0",
3329
"rgbpp": "0.0.0-snap-20240813134030",
3430
"rxjs": "^7.8.1",
31+
"snakecase-keys": "^8.0.1",
3532
"zod": "^3.23.8"
3633
},
3734
"devDependencies": {
3835
"@nestjs/cli": "^10.0.0",
3936
"@nestjs/schematics": "^10.0.0",
4037
"@nestjs/testing": "^10.0.0",
41-
"@types/jest": "^29.5.2",
38+
"@swc/core": "^1.7.11",
4239
"@types/node": "^20.3.1",
4340
"@types/supertest": "^6.0.0",
41+
"@vitest/coverage-v8": "^2.0.5",
4442
"source-map-support": "^0.5.21",
4543
"supertest": "^6.3.3",
46-
"ts-jest": "^29.1.0",
4744
"ts-loader": "^9.4.3",
4845
"ts-node": "^10.9.1",
4946
"tsconfig-paths": "^4.2.0",
5047
"type-fest": "^4.24.0",
51-
"typescript": "^5.1.3"
52-
},
53-
"jest": {
54-
"moduleFileExtensions": [
55-
"js",
56-
"json",
57-
"ts"
58-
],
59-
"rootDir": "src",
60-
"testRegex": ".*\\.spec\\.ts$",
61-
"transform": {
62-
"^.+\\.(t|j)s$": "ts-jest"
63-
},
64-
"collectCoverageFrom": [
65-
"**/*.(t|j)s"
66-
],
67-
"coverageDirectory": "../coverage",
68-
"testEnvironment": "node"
48+
"typescript": "^5.1.3",
49+
"unplugin-swc": "^1.5.1"
6950
}
7051
}

‎apps/service/src/utils/case.ts

+21-18
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
import { SnakeCasedPropertiesDeep, CamelCasedPropertiesDeep } from 'type-fest';
2-
import { toSnake, toCamel } from 'convert-keys';
1+
import type { SnakeCasedPropertiesDeep, CamelCasedPropertiesDeep } from 'type-fest';
2+
import snakeCaseKeys from 'snakecase-keys';
3+
import camelCaseKeys from 'camelcase-keys';
34

45
export type SnakeCased<T> = SnakeCasedPropertiesDeep<T>;
56
export type CamelCased<T> = CamelCasedPropertiesDeep<T>;
67

7-
export const toSnakeCase = <T>(obj: T): SnakeCased<T> | null => {
8-
try {
9-
return toSnake(obj);
10-
} catch (error) {
11-
console.error(error);
12-
}
13-
return null;
14-
};
8+
// This regex is used to exclude hex strings from being converted to snake_case or camelCase
9+
// Because hex strings in object keys should be kept as is
10+
const excludeHexRegex = /^0x.+/g;
1511

16-
export const toCamelCase = <T>(obj: T): CamelCased<T> | null => {
17-
try {
18-
return toCamel(obj);
19-
} catch (error) {
20-
console.error(error);
21-
}
22-
return null;
23-
};
12+
export function toSnakeCase<T extends object>(obj: T, options?: snakeCaseKeys.Options): SnakeCased<T> {
13+
return snakeCaseKeys(obj as Record<string, unknown>, {
14+
exclude: [excludeHexRegex],
15+
deep: true,
16+
...options,
17+
}) as SnakeCased<T>;
18+
}
19+
20+
export function toCamelCase<T>(obj: T, options?: camelCaseKeys.Options): CamelCased<T> {
21+
return camelCaseKeys(obj as Record<string, unknown>, {
22+
exclude: [excludeHexRegex],
23+
deep: true,
24+
...options,
25+
}) as CamelCased<T>;
26+
}

‎apps/service/src/utils/json.ts

+1-5
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,8 @@ export function ensureSafeJson<Input extends object, Output = Input>(json: Input
66
}
77

88
const obj = Array.isArray(json) ? [] : {};
9-
for (let key of Object.keys(json)) {
9+
for (const key of Object.keys(json)) {
1010
const value = json[key];
11-
// XXX: Remove underscores from hex strings due to toSnakeCase() transformation issue
12-
if (key.startsWith('0_x')) {
13-
key = key.replaceAll('_', '');
14-
}
1511
if (isPlainObject(value) || Array.isArray(value)) {
1612
obj[key] = ensureSafeJson(value);
1713
} else {

‎apps/service/tests/Utils.test.ts

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { ensureSafeJson } from '../src/utils/json';
3+
import { toSnakeCase, toCamelCase } from '../src/utils/case';
4+
5+
describe('Utils', () => {
6+
it('toSnakeCase()', () => {
7+
expect(
8+
toSnakeCase({
9+
misterA: 1,
10+
MisterB: 2,
11+
mister_C: 3,
12+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
13+
}),
14+
).toStrictEqual({
15+
mister_a: 1,
16+
mister_b: 2,
17+
mister_c: 3,
18+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
19+
});
20+
expect(
21+
toSnakeCase([
22+
{
23+
misterA: 1,
24+
MisterB: 2,
25+
mister_C: 3,
26+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
27+
},
28+
]),
29+
).toStrictEqual([
30+
{
31+
mister_a: 1,
32+
mister_b: 2,
33+
mister_c: 3,
34+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
35+
},
36+
]);
37+
});
38+
it('toCamelCase()', () => {
39+
expect(
40+
toCamelCase({
41+
misterA: 1,
42+
mister_b: 2,
43+
MisterC: 3,
44+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
45+
}),
46+
).toStrictEqual({
47+
misterA: 1,
48+
misterB: 2,
49+
misterC: 3,
50+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
51+
});
52+
expect(
53+
toCamelCase([
54+
{
55+
misterA: 1,
56+
mister_b: 2,
57+
MisterC: 3,
58+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
59+
},
60+
]),
61+
).toStrictEqual([
62+
{
63+
misterA: 1,
64+
misterB: 2,
65+
misterC: 3,
66+
'0x06c1c265d475e69bac3b42f8deca5ac982efabfa640eff96a0f5d15345583e6e': 4,
67+
},
68+
]);
69+
});
70+
it('ensureSafeJson()', () => {
71+
expect(
72+
ensureSafeJson({
73+
number: 1,
74+
boolean: true,
75+
string: 'string',
76+
object: {
77+
number: 1,
78+
bigint1: BigInt('0x64'),
79+
},
80+
array: [
81+
{
82+
number: 1,
83+
bigint1: BigInt('0x64'),
84+
},
85+
],
86+
bigint1: BigInt(100),
87+
bigint2: BigInt('100'),
88+
bigint3: BigInt('0x64'),
89+
}),
90+
).toStrictEqual({
91+
number: 1,
92+
boolean: true,
93+
string: 'string',
94+
object: {
95+
number: 1,
96+
bigint1: '0x64',
97+
},
98+
array: [
99+
{
100+
number: 1,
101+
bigint1: '0x64',
102+
},
103+
],
104+
bigint1: '0x64',
105+
bigint2: '0x64',
106+
bigint3: '0x64',
107+
});
108+
});
109+
});

‎apps/service/tsconfig.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
22
"compilerOptions": {
3-
"module": "NodeNext",
3+
"module": "ESNext",
44
"declaration": true,
55
"resolveJsonModule": true,
66
"removeComments": true,
7-
"moduleResolution": "NodeNext",
7+
"moduleResolution": "Bundler",
88
"emitDecoratorMetadata": true,
99
"experimentalDecorators": true,
1010
"allowSyntheticDefaultImports": true,
@@ -19,5 +19,6 @@
1919
"strictBindCallApply": false,
2020
"forceConsistentCasingInFileNames": false,
2121
"noFallthroughCasesInSwitch": false
22-
}
22+
},
23+
"exclude": ["dist"]
2324
}

‎apps/service/vitest.project.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import swc from 'unplugin-swc';
2+
import { defineConfig } from 'vitest/config';
3+
4+
export default defineConfig({
5+
test: {
6+
root: './',
7+
},
8+
plugins: [
9+
// This is required to build the test files with SWC
10+
swc.vite({
11+
// Explicitly set the module type to avoid inheriting this value from a `.swcrc` config file
12+
module: { type: 'es6' },
13+
}),
14+
],
15+
});

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
"scripts": {
88
"prepare": "husky",
99
"build": "pnpm run --r --filter \"./{packages,apps,examples,tests}/**\" build",
10-
"test:packages": "pnpm run --r --filter \"./packages/**\" test",
1110
"build:packages": "pnpm run --r --filter \"./packages/**\" build",
11+
"test:packages": "pnpm run --r --filter \"./packages/**\" test",
12+
"test:service": "pnpm run --r --filter=./apps/service test",
1213
"dev:service": "pnpm run --r --filter=./apps/service dev",
1314
"lint": "eslint {packages,apps,examples,tests}/**/*.ts && prettier --check '{packages,apps,examples,tests}/**/*.ts'",
1415
"lint:fix": "eslint --fix {packages,apps,examples,tests}/**/*.ts",

‎pnpm-lock.yaml

+326-1,789
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎vitest.workspace.mts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export default [
2+
'packages/*',
3+
'apps/*',
4+
];

0 commit comments

Comments
 (0)
Please sign in to comment.