Skip to content

Commit aa462a7

Browse files
committed
test(utils): add unit tests for utility functions
Add comprehensive unit tests for the utils module to ensure proper functionality and prevent regressions. 新增对 utils 模块的全面单元测试,以确保功能正常并防止回归问题。 Change-Id: Ia91ad346ad2036bcc6a3dd81c9b055c7810e5443 Signed-off-by: OhYee <oyohyee@oyohyee.com>
1 parent 25e4a65 commit aa462a7

1 file changed

Lines changed: 158 additions & 0 deletions

File tree

tests/unittests/utils.test.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { Config } from '../../src/utils/config';
2+
import {
3+
AgentRunError,
4+
HTTPError,
5+
ResourceNotExistError,
6+
ResourceAlreadyExistError,
7+
ClientError,
8+
} from '../../src/utils/exception';
9+
import * as model from '../../src/utils/model';
10+
import { mixin } from '../../src/utils/mixin';
11+
import {
12+
updateObjectProperties,
13+
listAllResourcesFunction,
14+
ResourceBase,
15+
} from '../../src/utils/resource';
16+
import { DataAPI, ResourceType } from '../../src/utils/data-api';
17+
18+
describe('utils - config', () => {
19+
test('config defaults and data/control/devs endpoints and accountId error', () => {
20+
// ensure we don't rely on external environment variables for accountId test
21+
const cfg = new Config({ regionId: 'cn-test', accountId: 'explicit' });
22+
expect(cfg.regionId).toBe('cn-test');
23+
expect(cfg.timeout).toBe(600000);
24+
expect(cfg.readTimeout).toBe(100000000);
25+
// controlEndpoint default builds from region
26+
expect(cfg.controlEndpoint).toContain('agentrun.cn-test.aliyuncs.com');
27+
28+
// when accountId explicitly set to empty string, getter should throw
29+
const cfgNoAccount = new Config({ regionId: 'cn-test', accountId: '' });
30+
expect(() => cfgNoAccount.accountId).toThrow();
31+
});
32+
33+
test('withConfigs merge and headers merging', () => {
34+
// avoid asserting on accessKey values because CI/dev environment may set env vars
35+
const a = new Config({ accessKeyId: 'a', headers: { a: '1' } });
36+
const b = new Config({ accessKeySecret: 's', headers: { b: '2' } });
37+
const merged = Config.withConfigs(a, b);
38+
expect(merged.headers).toMatchObject({ a: '1', b: '2' });
39+
});
40+
});
41+
42+
describe('utils - exception', () => {
43+
test('HTTPError toResourceError branches', () => {
44+
const e404 = new HTTPError(404, 'not');
45+
const r = e404.toResourceError('foo', 'id');
46+
expect(r).toBeInstanceOf(ResourceNotExistError);
47+
48+
const e409 = new HTTPError(409, 'conflict');
49+
const r2 = e409.toResourceError('foo');
50+
expect(r2).toBeInstanceOf(ResourceAlreadyExistError);
51+
52+
const e400 = new HTTPError(400, 'already exists somewhere');
53+
const r3 = e400.toResourceError('x');
54+
expect(r3).toBeInstanceOf(ResourceAlreadyExistError);
55+
56+
const eOther = new HTTPError(418, "I'm a teapot");
57+
const r4 = eOther.toResourceError('x');
58+
expect(r4).toBe(eOther);
59+
});
60+
});
61+
62+
describe('utils - model functions', () => {
63+
test('case conversion and removeUndefined and fromInnerObject', () => {
64+
expect(model.toSnakeCase('helloWorld')).toBe('hello_world');
65+
expect(model.toCamelCase('hello_world')).toBe('helloWorld');
66+
67+
const nested = { aValue: 1, inner: { nestedValue: 2 }, arr: [{ subVal: 3 }] };
68+
const snake = model.toSnakeCaseKeys(nested as any);
69+
expect(Object.keys(snake)).toContain('a_value');
70+
expect((snake as any).inner).toBeDefined();
71+
72+
const camel = model.toCamelCaseKeys({ a_value: 1, inner: { nested_value: 2 }, arr: [{ sub_val: 3 }] } as any);
73+
expect(Object.keys(camel)).toContain('aValue');
74+
75+
expect(model.removeUndefined({ a: 1, b: undefined })).toEqual({ a: 1 });
76+
77+
// isFinalStatus true for READY and true for undefined
78+
expect(model.isFinalStatus('READY')).toBe(true);
79+
expect(model.isFinalStatus(undefined)).toBe(true);
80+
81+
// fromInnerObject merges extra
82+
const obj = { x: 1 };
83+
const merged = model.fromInnerObject(obj, { y: 2 });
84+
expect((merged as any).y).toBe(2);
85+
});
86+
});
87+
88+
describe('utils - mixin', () => {
89+
test('mixin copies prototype and static properties', () => {
90+
class A {
91+
static staticA = 1;
92+
protoA() { return 'a'; }
93+
}
94+
95+
class B { }
96+
97+
const Mixed = mixin(A as any, B as any) as any;
98+
// static prop copied
99+
expect(Mixed.staticA).toBe(1);
100+
// prototype method available
101+
const inst = new Mixed();
102+
expect(typeof inst.protoA).toBe('function');
103+
});
104+
});
105+
106+
describe('utils - resource helpers', () => {
107+
test('updateObjectProperties only copies data fields', () => {
108+
const target: any = {};
109+
const src: any = { a: 1, b: () => 2, _c: 3 };
110+
updateObjectProperties(target, src);
111+
expect(target.a).toBe(1);
112+
expect(target.b).toBeUndefined();
113+
expect(target._c).toBeUndefined();
114+
});
115+
116+
test('listAllResourcesFunction paginates and dedups', async () => {
117+
// listAllResourcesFunction uses a pageSize of 50 internally.
118+
// To exercise pagination we return 50 items on first call and a small last page on second.
119+
const firstPage = Array.from({ length: 50 }, (_, i) => ({ uniqIdCallback: () => `${i + 1}` }));
120+
const secondPage = [{ uniqIdCallback: () => '50' }, { uniqIdCallback: () => '51' }];
121+
const pages = [firstPage, secondPage];
122+
let called = 0;
123+
const list = async (params?: any) => {
124+
return pages[called++] || [];
125+
};
126+
127+
const all = listAllResourcesFunction(list as any);
128+
const res = await all({});
129+
// expect unique ids from 1..51 -> 51 results
130+
expect(res.length).toBe(51);
131+
});
132+
});
133+
134+
describe('utils - data-api small surface', () => {
135+
test('withPath merges queries and arrays', () => {
136+
const cfg = new Config({ token: 't', accountId: 'acct' });
137+
const d = new DataAPI('name', ResourceType.Runtime, cfg, 'ns');
138+
const url = d.withPath('/some/path', { a: [1, 2], b: 'x' });
139+
expect(url).toContain('a=1');
140+
expect(url).toContain('a=2');
141+
expect(url).toContain('b=x');
142+
});
143+
144+
test('prepareRequest handles body types and sets token header', async () => {
145+
const cfg = new Config({ token: 'tok', accountId: 'acct' });
146+
const d = new DataAPI('name', ResourceType.Runtime, cfg, 'ns') as any;
147+
148+
const r1 = await d.prepareRequest('POST', 'https://example.com/x', { a: 1 }, { H: 'v' });
149+
expect(r1.body).toBeDefined();
150+
expect(r1.headers['Agentrun-Access-Token']).toBe('tok');
151+
152+
const r2 = await d.prepareRequest('PUT', 'https://example.com/x', Buffer.from('hi'), {});
153+
expect(Buffer.isBuffer(r2.body)).toBe(true);
154+
155+
const r3 = await d.prepareRequest('PATCH', 'https://example.com/x', 'rawtext', {});
156+
expect(r3.body).toBe('rawtext');
157+
});
158+
});

0 commit comments

Comments
 (0)