Skip to content

Commit be78783

Browse files
Merge pull request #258 from contentstack/fix/dx-3674-improve-testcases-coverage
Fix/dx 3674 improve testcases coverage
2 parents daae12d + 5d6703b commit be78783

13 files changed

Lines changed: 1307 additions & 3 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: 'TS SDK - Unit Testing'
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- development
7+
- staging
8+
- main
9+
10+
jobs:
11+
coverage:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
pull-requests: write
16+
checks: write
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Run unit tests with coverage
22+
uses: ArtiomTr/jest-coverage-report-action@v2
23+
id: coverage
24+
with:
25+
test-script: npm run test:unit
26+
threshold: 95

test/unit/asset-query.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ describe('AssetQuery class', () => {
4848
expect(assetQuery._queryParams.include_fallback).toBe('true');
4949
});
5050

51+
it('should add "include_metadata" in queryParameter when includeMetadata method is called', () => {
52+
const returnedValue = assetQuery.includeMetadata();
53+
expect(returnedValue).toBeInstanceOf(AssetQuery);
54+
expect(assetQuery._queryParams.include_metadata).toBe('true');
55+
});
56+
5157
it('should add "locale" in Parameter when locale method is called', () => {
5258
const returnedValue = assetQuery.locale('en-us');
5359
expect(returnedValue).toBeInstanceOf(AssetQuery);

test/unit/asset.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ describe('Asset class', () => {
4242
expect(asset._queryParams.include_fallback).toBe('true');
4343
});
4444

45+
it('should add "include_metadata" in _queryParams when includeMetadata method is called', () => {
46+
const returnedValue = asset.includeMetadata();
47+
expect(returnedValue).toBeInstanceOf(Asset);
48+
expect(asset._queryParams.include_metadata).toBe('true');
49+
});
50+
4551
it('should add "relative_urls" in _queryParams when relativeUrl method is called', () => {
4652
const returnedValue = asset.relativeUrls();
4753
expect(returnedValue).toBeInstanceOf(Asset);
@@ -59,4 +65,13 @@ describe('Asset class', () => {
5965
const returnedValue = await asset.fetch();
6066
expect(returnedValue).toEqual(assetFetchDataMock.asset);
6167
});
68+
69+
it('should return response directly when asset property is not present', async () => {
70+
const responseWithoutAsset = { data: 'test', uid: 'test-uid' };
71+
mockClient.onGet(`/assets/assetUid`).reply(200, responseWithoutAsset);
72+
73+
const result = await asset.fetch();
74+
75+
expect(result).toEqual(responseWithoutAsset);
76+
});
6277
});

test/unit/base-query.spec.ts

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { BaseQuery } from '../../src/lib/base-query';
2+
import { httpClient, AxiosInstance } from '@contentstack/core';
3+
import { MOCK_CLIENT_OPTIONS } from '../utils/constant';
4+
import MockAdapter from 'axios-mock-adapter';
5+
import { entryFindMock } from '../utils/mocks';
26

37
describe('BaseQuery class', () => {
48
let baseQuery: BaseQuery;
@@ -69,4 +73,109 @@ describe('BaseQuery class', () => {
6973
baseQuery.removeParam('key2');
7074
expect(baseQuery._queryParams).toEqual({ key1: 'value1' });
7175
});
76+
});
77+
78+
class TestableBaseQuery extends BaseQuery {
79+
constructor(client: AxiosInstance, urlPath: string | null = null) {
80+
super();
81+
this._client = client;
82+
if (urlPath !== null) {
83+
this._urlPath = urlPath;
84+
}
85+
this._variants = '';
86+
}
87+
88+
setVariants(variants: string) {
89+
this._variants = variants;
90+
}
91+
92+
setParameters(params: any) {
93+
this._parameters = params;
94+
}
95+
96+
setUrlPath(path: string) {
97+
this._urlPath = path;
98+
}
99+
}
100+
101+
describe('BaseQuery find method', () => {
102+
let client: AxiosInstance;
103+
let mockClient: MockAdapter;
104+
let query: TestableBaseQuery;
105+
106+
beforeAll(() => {
107+
client = httpClient(MOCK_CLIENT_OPTIONS);
108+
mockClient = new MockAdapter(client as any);
109+
});
110+
111+
beforeEach(() => {
112+
query = new TestableBaseQuery(client, '/content_types/test_uid/entries');
113+
mockClient.reset();
114+
});
115+
116+
it('should call find with encode parameter true', async () => {
117+
mockClient.onGet('/content_types/test_uid/entries').reply(200, entryFindMock);
118+
119+
query.setParameters({ title: 'Test' });
120+
const result = await query.find(true);
121+
122+
expect(result).toEqual(entryFindMock);
123+
});
124+
125+
it('should call find without parameters', async () => {
126+
mockClient.onGet('/content_types/test_uid/entries').reply(200, entryFindMock);
127+
128+
const result = await query.find();
129+
130+
expect(result).toEqual(entryFindMock);
131+
});
132+
133+
it('should call find with variants header when variants are set', async () => {
134+
mockClient.onGet('/content_types/test_uid/entries').reply((config) => {
135+
expect(config.headers?.['x-cs-variant-uid']).toBe('variant1,variant2');
136+
return [200, entryFindMock];
137+
});
138+
139+
query.setVariants('variant1,variant2');
140+
await query.find();
141+
});
142+
143+
it('should extract content type UID from URL path', async () => {
144+
mockClient.onGet('/content_types/my_content_type/entries').reply(200, entryFindMock);
145+
146+
const queryWithContentType = new TestableBaseQuery(client, '/content_types/my_content_type/entries');
147+
const result = await queryWithContentType.find();
148+
149+
expect(result).toEqual(entryFindMock);
150+
});
151+
152+
it('should return null for content type UID when URL does not match pattern', async () => {
153+
mockClient.onGet('/assets').reply(200, entryFindMock);
154+
155+
const queryWithoutContentType = new TestableBaseQuery(client, '/assets');
156+
const result = await queryWithoutContentType.find();
157+
158+
expect(result).toEqual(entryFindMock);
159+
});
160+
161+
it('should handle find with both encode and variants', async () => {
162+
mockClient.onGet('/content_types/test_uid/entries').reply((config) => {
163+
expect(config.headers?.['x-cs-variant-uid']).toBe('test-variant');
164+
return [200, entryFindMock];
165+
});
166+
167+
query.setVariants('test-variant');
168+
query.setParameters({ status: 'published' });
169+
const result = await query.find(true);
170+
171+
expect(result).toEqual(entryFindMock);
172+
});
173+
174+
it('should handle empty _urlPath gracefully', () => {
175+
const queryWithoutUrlPath = new TestableBaseQuery(client, null);
176+
queryWithoutUrlPath.setUrlPath('');
177+
178+
// Verify that URL path is empty (testing the null check in extractContentTypeUidFromUrl)
179+
expect(queryWithoutUrlPath).toBeInstanceOf(TestableBaseQuery);
180+
});
72181
});

test/unit/cache.spec.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,4 +329,93 @@ describe("Cache handleRequest function", () => {
329329
cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid);
330330
});
331331
});
332+
333+
describe("Enhanced cache key with entryUid", () => {
334+
it("should extract entryUid from URL pattern", async () => {
335+
const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 };
336+
const defaultAdapter = jest.fn((_config) => ({
337+
data: JSON.stringify("foo"),
338+
}));
339+
const configWithUrl = {
340+
...config,
341+
url: '/content_types/test_ct/entries/entry123',
342+
};
343+
344+
const cacheStore = new PersistanceStore(cacheOptions);
345+
346+
await handleRequest(
347+
cacheOptions,
348+
apiKey,
349+
defaultAdapter,
350+
resolve,
351+
reject,
352+
configWithUrl
353+
);
354+
355+
expect(defaultAdapter).toHaveBeenCalled();
356+
expect(resolve).toBeCalledWith({ data: "foo" });
357+
358+
// Clean up with enhanced key that includes entry UID
359+
const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}_entry_entry123`;
360+
cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid);
361+
});
362+
363+
it("should use entryUid from config when available", async () => {
364+
const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 };
365+
const defaultAdapter = jest.fn((_config) => ({
366+
data: JSON.stringify("foo"),
367+
}));
368+
const configWithEntryUid = {
369+
...config,
370+
entryUid: 'entry456',
371+
};
372+
373+
const cacheStore = new PersistanceStore(cacheOptions);
374+
375+
await handleRequest(
376+
cacheOptions,
377+
apiKey,
378+
defaultAdapter,
379+
resolve,
380+
reject,
381+
configWithEntryUid
382+
);
383+
384+
expect(defaultAdapter).toHaveBeenCalled();
385+
expect(resolve).toBeCalledWith({ data: "foo" });
386+
387+
// Clean up with enhanced key that includes entry UID
388+
const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}_entry_entry456`;
389+
cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid);
390+
});
391+
392+
it("should return null when URL does not match entry pattern", async () => {
393+
const cacheOptions = { policy: Policy.CACHE_THEN_NETWORK, maxAge: 3600 };
394+
const defaultAdapter = jest.fn((_config) => ({
395+
data: JSON.stringify("foo"),
396+
}));
397+
const configWithInvalidUrl = {
398+
...config,
399+
url: '/assets',
400+
};
401+
402+
const cacheStore = new PersistanceStore(cacheOptions);
403+
404+
await handleRequest(
405+
cacheOptions,
406+
apiKey,
407+
defaultAdapter,
408+
resolve,
409+
reject,
410+
configWithInvalidUrl
411+
);
412+
413+
expect(defaultAdapter).toHaveBeenCalled();
414+
expect(resolve).toBeCalledWith({ data: "foo" });
415+
416+
// Clean up with standard enhanced key (no entry UID)
417+
const enhancedCacheKey = `${config.contentTypeUid}_${apiKey}`;
418+
cacheStore.removeItem(enhancedCacheKey, config.contentTypeUid);
419+
});
420+
});
332421
});

0 commit comments

Comments
 (0)