Skip to content

Commit 5ec9c0b

Browse files
committed
Add api-test tests
1 parent 1c9dc50 commit 5ec9c0b

File tree

5 files changed

+796
-0
lines changed

5 files changed

+796
-0
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// Copyright © 2025 Hardcore Engineering Inc.
3+
//
4+
5+
import { loadServerConfig } from '../config'
6+
7+
describe('loadServerConfig', () => {
8+
const mockFetch = jest.fn()
9+
global.fetch = mockFetch as any
10+
11+
beforeEach(() => {
12+
mockFetch.mockClear()
13+
})
14+
15+
it('should load server config successfully', async () => {
16+
const mockConfig = {
17+
ACCOUNTS_URL: 'https://accounts.example.com',
18+
COLLABORATOR_URL: 'https://collaborator.example.com',
19+
FILES_URL: 'https://files.example.com',
20+
UPLOAD_URL: 'https://upload.example.com'
21+
}
22+
23+
mockFetch.mockResolvedValue({
24+
ok: true,
25+
json: async () => mockConfig
26+
})
27+
28+
const config = await loadServerConfig('https://api.example.com')
29+
30+
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/config.json', { keepalive: true })
31+
expect(config).toEqual(mockConfig)
32+
})
33+
34+
it('should throw error when fetch fails', async () => {
35+
mockFetch.mockResolvedValue({
36+
ok: false,
37+
status: 404
38+
})
39+
40+
await expect(loadServerConfig('https://api.example.com')).rejects.toThrow('Failed to fetch config')
41+
})
42+
43+
it('should handle network errors', async () => {
44+
mockFetch.mockRejectedValue(new Error('Network error'))
45+
46+
await expect(loadServerConfig('https://api.example.com')).rejects.toThrow('Network error')
47+
})
48+
49+
it('should construct correct config URL', async () => {
50+
mockFetch.mockResolvedValue({
51+
ok: true,
52+
json: async () => ({
53+
ACCOUNTS_URL: '',
54+
COLLABORATOR_URL: '',
55+
FILES_URL: '',
56+
UPLOAD_URL: ''
57+
})
58+
})
59+
60+
await loadServerConfig('https://api.example.com/')
61+
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/config.json', { keepalive: true })
62+
})
63+
64+
it('should handle URL without trailing slash', async () => {
65+
mockFetch.mockResolvedValue({
66+
ok: true,
67+
json: async () => ({
68+
ACCOUNTS_URL: '',
69+
COLLABORATOR_URL: '',
70+
FILES_URL: '',
71+
UPLOAD_URL: ''
72+
})
73+
})
74+
75+
await loadServerConfig('https://api.example.com')
76+
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/config.json', { keepalive: true })
77+
})
78+
})
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//
2+
// Copyright © 2025 Hardcore Engineering Inc.
3+
//
4+
5+
import { createMarkupOperations } from '../markup/client'
6+
import { getClient } from '@hcengineering/collaborator-client'
7+
import { makeCollabId } from '@hcengineering/core'
8+
9+
// Mock dependencies
10+
jest.mock('@hcengineering/collaborator-client')
11+
jest.mock('@hcengineering/text', () => ({
12+
htmlToJSON: jest.fn((html) => ({ type: 'doc', content: [{ type: 'text', text: html }] })),
13+
jsonToHTML: jest.fn((json) => json.content?.[0]?.text ?? ''),
14+
jsonToMarkup: jest.fn((json) => json.content?.[0]?.text ?? ''),
15+
markupToJSON: jest.fn((markup) => ({ type: 'doc', content: [{ type: 'text', text: markup }] }))
16+
}))
17+
jest.mock('@hcengineering/text-markdown', () => ({
18+
markdownToMarkup: jest.fn((md) => md),
19+
markupToMarkdown: jest.fn((json) => json.content?.[0]?.text ?? '')
20+
}))
21+
22+
describe('MarkupOperations', () => {
23+
const mockConfig = {
24+
ACCOUNTS_URL: 'https://accounts.example.com',
25+
COLLABORATOR_URL: 'https://collaborator.example.com',
26+
FILES_URL: 'https://files.example.com',
27+
UPLOAD_URL: 'https://upload.example.com'
28+
}
29+
30+
const workspace = 'test-workspace' as any
31+
const token = 'test-token'
32+
const url = 'https://api.example.com'
33+
34+
let mockCollaborator: any
35+
let operations: any
36+
37+
beforeEach(() => {
38+
jest.clearAllMocks()
39+
40+
mockCollaborator = {
41+
getMarkup: jest.fn(),
42+
createMarkup: jest.fn()
43+
}
44+
;(getClient as jest.Mock).mockReturnValue(mockCollaborator)
45+
46+
operations = createMarkupOperations(url, workspace, token, mockConfig)
47+
})
48+
49+
describe('fetchMarkup', () => {
50+
const objectClass = 'class:test.Doc' as any
51+
const objectId = 'doc-id-123' as any
52+
const objectAttr = 'content'
53+
const markupRef = 'markup-ref-456' as any
54+
55+
it('should fetch markup in markup format', async () => {
56+
const mockMarkup = 'Test markup content'
57+
mockCollaborator.getMarkup.mockResolvedValue(mockMarkup)
58+
59+
const result = await operations.fetchMarkup(objectClass, objectId, objectAttr, markupRef, 'markup')
60+
61+
const collabId = makeCollabId(objectClass, objectId, objectAttr)
62+
expect(mockCollaborator.getMarkup).toHaveBeenCalledWith(collabId, markupRef)
63+
expect(result).toBe(mockMarkup)
64+
})
65+
66+
it('should fetch markup in HTML format', async () => {
67+
const mockMarkup = '<p>Test content</p>'
68+
mockCollaborator.getMarkup.mockResolvedValue(mockMarkup)
69+
70+
const result = await operations.fetchMarkup(objectClass, objectId, objectAttr, markupRef, 'html')
71+
72+
expect(mockCollaborator.getMarkup).toHaveBeenCalled()
73+
expect(result).toBeDefined()
74+
})
75+
76+
it('should fetch markup in markdown format', async () => {
77+
const mockMarkup = '# Test heading'
78+
mockCollaborator.getMarkup.mockResolvedValue(mockMarkup)
79+
80+
const result = await operations.fetchMarkup(objectClass, objectId, objectAttr, markupRef, 'markdown')
81+
82+
expect(mockCollaborator.getMarkup).toHaveBeenCalled()
83+
expect(result).toBeDefined()
84+
})
85+
86+
it('should throw error for unknown format', async () => {
87+
mockCollaborator.getMarkup.mockResolvedValue('content')
88+
89+
await expect(
90+
operations.fetchMarkup(objectClass, objectId, objectAttr, markupRef, 'unknown-format' as any)
91+
).rejects.toThrow('Unknown content format')
92+
})
93+
94+
it('should handle collaborator errors', async () => {
95+
mockCollaborator.getMarkup.mockRejectedValue(new Error('Collaborator error'))
96+
97+
await expect(operations.fetchMarkup(objectClass, objectId, objectAttr, markupRef, 'markup')).rejects.toThrow(
98+
'Collaborator error'
99+
)
100+
})
101+
})
102+
103+
describe('uploadMarkup', () => {
104+
const objectClass = 'class:test.Doc' as any
105+
const objectId = 'doc-id-123' as any
106+
const objectAttr = 'content'
107+
const mockMarkupRef = 'new-markup-ref-789' as any
108+
109+
beforeEach(() => {
110+
mockCollaborator.createMarkup.mockResolvedValue(mockMarkupRef)
111+
})
112+
113+
it('should upload markup in markup format', async () => {
114+
const content = 'Test markup content'
115+
116+
const result = await operations.uploadMarkup(objectClass, objectId, objectAttr, content, 'markup')
117+
118+
const collabId = makeCollabId(objectClass, objectId, objectAttr)
119+
expect(mockCollaborator.createMarkup).toHaveBeenCalledWith(collabId, content)
120+
expect(result).toBe(mockMarkupRef)
121+
})
122+
123+
it('should upload markup in HTML format', async () => {
124+
const content = '<p>Test HTML content</p>'
125+
126+
const result = await operations.uploadMarkup(objectClass, objectId, objectAttr, content, 'html')
127+
128+
expect(mockCollaborator.createMarkup).toHaveBeenCalled()
129+
expect(result).toBe(mockMarkupRef)
130+
})
131+
132+
it('should upload markup in markdown format', async () => {
133+
const content = '# Test markdown'
134+
135+
const result = await operations.uploadMarkup(objectClass, objectId, objectAttr, content, 'markdown')
136+
137+
expect(mockCollaborator.createMarkup).toHaveBeenCalled()
138+
expect(result).toBe(mockMarkupRef)
139+
})
140+
141+
it('should throw error for unknown format', async () => {
142+
await expect(
143+
operations.uploadMarkup(objectClass, objectId, objectAttr, 'content', 'unknown-format' as any)
144+
).rejects.toThrow('Unknown content format')
145+
})
146+
147+
it('should handle empty content', async () => {
148+
const result = await operations.uploadMarkup(objectClass, objectId, objectAttr, '', 'markup')
149+
150+
const collabId = makeCollabId(objectClass, objectId, objectAttr)
151+
expect(mockCollaborator.createMarkup).toHaveBeenCalledWith(collabId, '')
152+
expect(result).toBe(mockMarkupRef)
153+
})
154+
155+
it('should handle collaborator errors', async () => {
156+
mockCollaborator.createMarkup.mockRejectedValue(new Error('Upload failed'))
157+
158+
await expect(operations.uploadMarkup(objectClass, objectId, objectAttr, 'content', 'markup')).rejects.toThrow(
159+
'Upload failed'
160+
)
161+
})
162+
})
163+
164+
describe('initialization', () => {
165+
it('should initialize collaborator client with correct parameters', () => {
166+
expect(getClient).toHaveBeenCalledWith(workspace, token, mockConfig.COLLABORATOR_URL)
167+
})
168+
169+
it('should handle different workspace IDs', () => {
170+
const differentWorkspace = 'different-workspace' as any
171+
createMarkupOperations(url, differentWorkspace, token, mockConfig)
172+
173+
expect(getClient).toHaveBeenCalledWith(differentWorkspace, token, mockConfig.COLLABORATOR_URL)
174+
})
175+
176+
it('should handle different tokens', () => {
177+
const differentToken = 'different-token'
178+
createMarkupOperations(url, workspace, differentToken, mockConfig)
179+
180+
expect(getClient).toHaveBeenCalledWith(workspace, differentToken, mockConfig.COLLABORATOR_URL)
181+
})
182+
})
183+
})
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//
2+
// Copyright © 2025 Hardcore Engineering Inc.
3+
//
4+
5+
import { html, markdown, MarkupContent } from '../markup/types'
6+
7+
describe('MarkupContent', () => {
8+
describe('constructor', () => {
9+
it('should create MarkupContent with content and kind', () => {
10+
const content = '<p>Hello World</p>'
11+
const markup = new MarkupContent(content, 'html')
12+
13+
expect(markup.content).toBe(content)
14+
expect(markup.kind).toBe('html')
15+
})
16+
17+
it('should create MarkupContent with markdown kind', () => {
18+
const content = '# Hello World'
19+
const markup = new MarkupContent(content, 'markdown')
20+
21+
expect(markup.content).toBe(content)
22+
expect(markup.kind).toBe('markdown')
23+
})
24+
25+
it('should create MarkupContent with markup kind', () => {
26+
const content = 'plain markup content'
27+
const markup = new MarkupContent(content, 'markup')
28+
29+
expect(markup.content).toBe(content)
30+
expect(markup.kind).toBe('markup')
31+
})
32+
})
33+
34+
describe('html helper', () => {
35+
it('should create HTML MarkupContent', () => {
36+
const content = '<h1>Title</h1><p>Content</p>'
37+
const markup = html(content)
38+
39+
expect(markup).toBeInstanceOf(MarkupContent)
40+
expect(markup.content).toBe(content)
41+
expect(markup.kind).toBe('html')
42+
})
43+
44+
it('should handle empty HTML', () => {
45+
const markup = html('')
46+
47+
expect(markup.content).toBe('')
48+
expect(markup.kind).toBe('html')
49+
})
50+
51+
it('should handle complex HTML with attributes', () => {
52+
const content = '<div class="container"><a href="https://example.com">Link</a></div>'
53+
const markup = html(content)
54+
55+
expect(markup.content).toBe(content)
56+
expect(markup.kind).toBe('html')
57+
})
58+
})
59+
60+
describe('markdown helper', () => {
61+
it('should create Markdown MarkupContent', () => {
62+
const content = '# Heading\n\n* List item 1\n* List item 2'
63+
const markup = markdown(content)
64+
65+
expect(markup).toBeInstanceOf(MarkupContent)
66+
expect(markup.content).toBe(content)
67+
expect(markup.kind).toBe('markdown')
68+
})
69+
70+
it('should handle empty markdown', () => {
71+
const markup = markdown('')
72+
73+
expect(markup.content).toBe('')
74+
expect(markup.kind).toBe('markdown')
75+
})
76+
77+
it('should handle markdown with code blocks', () => {
78+
const content = '```javascript\nconst x = 42;\n```'
79+
const markup = markdown(content)
80+
81+
expect(markup.content).toBe(content)
82+
expect(markup.kind).toBe('markdown')
83+
})
84+
85+
it('should handle markdown with links', () => {
86+
const content = '[Link text](https://example.com)'
87+
const markup = markdown(content)
88+
89+
expect(markup.content).toBe(content)
90+
expect(markup.kind).toBe('markdown')
91+
})
92+
})
93+
94+
describe('edge cases', () => {
95+
it('should handle special characters in content', () => {
96+
const content = '<p>Special chars: & < > " \'</p>'
97+
const markup = html(content)
98+
99+
expect(markup.content).toBe(content)
100+
})
101+
102+
it('should handle Unicode characters', () => {
103+
const content = '# 你好世界 🌍'
104+
const markup = markdown(content)
105+
106+
expect(markup.content).toBe(content)
107+
})
108+
109+
it('should handle very long content', () => {
110+
const content = 'a'.repeat(10000)
111+
const markup = html(content)
112+
113+
expect(markup.content).toBe(content)
114+
expect(markup.content.length).toBe(10000)
115+
})
116+
117+
it('should handle multiline content', () => {
118+
const content = 'Line 1\nLine 2\nLine 3'
119+
const markup = markdown(content)
120+
121+
expect(markup.content).toBe(content)
122+
})
123+
})
124+
})

0 commit comments

Comments
 (0)