spec-up-t 1.1.54 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/compiled/body.js +59 -8
- package/assets/compiled/head.css +13 -12
- package/assets/css/adjust-font-size.css +11 -0
- package/assets/css/collapse-meta-info.css +27 -8
- package/assets/css/create-term-filter.css +11 -0
- package/assets/css/index.css +15 -0
- package/assets/css/prism.css +176 -153
- package/assets/css/prism.dark.css +157 -0
- package/assets/css/prism.default.css +180 -0
- package/assets/css/terms-and-definitions.1.css +223 -0
- package/assets/css/terms-and-definitions.css +214 -100
- package/assets/js/addAnchorsToTerms.js +1 -1
- package/assets/js/collapse-definitions.js +2 -1
- package/assets/js/collapse-meta-info.js +25 -11
- package/assets/js/create-term-filter.js +61 -0
- package/assets/js/horizontal-scroll-hint.js +159 -0
- package/assets/js/index.1.js +137 -0
- package/assets/js/index.js +2 -1
- package/assets/js/insert-trefs.js +122 -116
- package/assets/js/insert-xrefs.1.js +372 -0
- package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
- package/assets/js/prism.dark.js +24 -0
- package/assets/js/prism.default.js +23 -0
- package/assets/js/prism.js +4 -5
- package/assets/js/search.js +1 -1
- package/assets/js/toggle-dense-info.js +40 -0
- package/branches.md +4 -29
- package/index.js +397 -189
- package/index.new.js +662 -0
- package/package.json +1 -1
- package/src/asset-map.json +9 -5
- package/src/collect-external-references.js +16 -9
- package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
- package/src/collectExternalReferences/processXTrefsData.js +73 -18
- package/src/create-pdf.js +385 -89
- package/src/health-check/external-specs-checker.js +207 -0
- package/src/health-check/output-gitignore-checker.js +261 -0
- package/src/health-check/specs-configuration-checker.js +274 -0
- package/src/health-check/term-references-checker.js +191 -0
- package/src/health-check/terms-intro-checker.js +81 -0
- package/src/health-check/tref-term-checker.js +463 -0
- package/src/health-check.js +445 -0
- package/src/install-from-boilerplate/config-scripts-keys.js +2 -0
- package/src/install-from-boilerplate/menu.sh +6 -3
- package/src/markdown-it-extensions.js +134 -103
- package/src/prepare-tref.js +61 -24
- package/src/utils/fetch.js +100 -0
- package/templates/template.html +12 -7
- package/assets/js/css-helper.js +0 -30
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
- package/src/collectExternalReferences/octokitClient.js +0 -96
|
@@ -1,232 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const crypto = require('crypto'); // For generating cache keys
|
|
4
|
-
const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWithDefinition;
|
|
5
|
-
const { addPath, getPath, getAllPaths } = require('../config/paths');
|
|
6
|
-
const { getSearchClient, getContentClient } = require('./octokitClient');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Directory to store cached files
|
|
10
|
-
const CACHE_DIR = getPath('githubcache');
|
|
11
|
-
|
|
12
|
-
// Helper function to generate a cache key
|
|
13
|
-
function generateCacheKey(...args) {
|
|
14
|
-
const hash = crypto.createHash('md5').update(args.join('-')).digest('hex');
|
|
15
|
-
return hash;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
async function fetchTermsFromGitHubRepository(GITHUB_API_TOKEN, searchString, owner, repo, subdirectory) {
|
|
26
|
-
console.log(`Searching for '${searchString}' in ${owner}/${repo}/${subdirectory}`);
|
|
27
|
-
try {
|
|
28
|
-
// Create directory if it doesn't exist
|
|
29
|
-
if (!fs.existsSync(CACHE_DIR)) {
|
|
30
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// Generate a cache key for the search query
|
|
34
|
-
const searchCacheKey = generateCacheKey('search', searchString, owner, repo, subdirectory);
|
|
35
|
-
const searchCacheFilePath = path.join(CACHE_DIR, `${searchCacheKey}.json`);
|
|
36
|
-
|
|
37
|
-
let searchResponse;
|
|
38
|
-
|
|
39
|
-
// Check if the search response is already cached
|
|
40
|
-
if (fs.existsSync(searchCacheFilePath)) {
|
|
41
|
-
console.log(`Serving search results from cache: ${searchCacheFilePath}`);
|
|
42
|
-
searchResponse = JSON.parse(fs.readFileSync(searchCacheFilePath, 'utf-8'));
|
|
43
|
-
} else {
|
|
44
|
-
// Get the search client
|
|
45
|
-
console.log(`Performing search and caching results: ${searchCacheFilePath}`);
|
|
46
|
-
const searchClient = await getSearchClient(GITHUB_API_TOKEN);
|
|
47
|
-
|
|
48
|
-
// Perform the search
|
|
49
|
-
searchResponse = await searchClient.search(searchString, owner, repo, subdirectory);
|
|
50
|
-
|
|
51
|
-
// Cache the search response
|
|
52
|
-
fs.writeFileSync(searchCacheFilePath, JSON.stringify(searchResponse), 'utf-8');
|
|
53
|
-
}
|
|
54
|
-
// After search
|
|
55
|
-
console.log(`Search found ${searchResponse.data.total_count} results`);
|
|
56
|
-
if (searchResponse.data.total_count === 0) {
|
|
57
|
-
console.log("No matches found - check if term exists in repository");
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/*
|
|
62
|
-
|
|
63
|
-
Each item is a file that contains the search string one or more times. So if a search string is found in 'attribute-based-access-control.md' and 'abac.md', both files will be returned as separate items. Each item contains “text_matches”.
|
|
64
|
-
|
|
65
|
-
- text_matches can contain multiple objects if there are multiple matches in the file.
|
|
66
|
-
- fragment is a snippet of the file content around the matched search string, not the entire file content.
|
|
67
|
-
|
|
68
|
-
In example below:
|
|
69
|
-
|
|
70
|
-
- The total_count is 2, indicating there are two files that contain the search string.
|
|
71
|
-
- Each item in the items array represents a file.
|
|
72
|
-
- The text_matches array within each item contains objects representing different matches of the search string within the file.
|
|
73
|
-
- Each fragment is a snippet of the file content around the matched search string, not the entire file content.
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
"total_count": 2,
|
|
77
|
-
"items": [
|
|
78
|
-
{
|
|
79
|
-
"name": "example-file1.md",
|
|
80
|
-
"path": "docs/example-file1.md",
|
|
81
|
-
"sha": "abc123",
|
|
82
|
-
"url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
|
|
83
|
-
"git_url": "https://api.github.com/repositories/123456789/git/blobs/abc123",
|
|
84
|
-
"html_url": "https://github.com/owner/repo/blob/main/docs/example-file1.md",
|
|
85
|
-
"repository": {
|
|
86
|
-
"id": 123456789,
|
|
87
|
-
"name": "repo",
|
|
88
|
-
"full_name": "owner/repo",
|
|
89
|
-
"owner": {
|
|
90
|
-
"login": "owner",
|
|
91
|
-
"id": 12345,
|
|
92
|
-
"avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
|
|
93
|
-
"url": "https://api.github.com/users/owner"
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
"text_matches": [
|
|
97
|
-
{
|
|
98
|
-
"object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
|
|
99
|
-
"object_type": "FileContent",
|
|
100
|
-
"property": "content",
|
|
101
|
-
"fragment": "This is an example content with the search string.",
|
|
102
|
-
"matches": [
|
|
103
|
-
{
|
|
104
|
-
"text": "search string",
|
|
105
|
-
"indices": [31, 44]
|
|
106
|
-
}
|
|
107
|
-
]
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
"object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file1.md",
|
|
111
|
-
"object_type": "FileContent",
|
|
112
|
-
"property": "content",
|
|
113
|
-
"fragment": "Another occurrence of the search string in the file.",
|
|
114
|
-
"matches": [
|
|
115
|
-
{
|
|
116
|
-
"text": "search string",
|
|
117
|
-
"indices": [25, 38]
|
|
118
|
-
}
|
|
119
|
-
]
|
|
120
|
-
}
|
|
121
|
-
]
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
"name": "example-file2.md",
|
|
125
|
-
"path": "docs/example-file2.md",
|
|
126
|
-
"sha": "def456",
|
|
127
|
-
"url": "https://api.github.com/repositories/123456789/contents/docs/example-file2.md",
|
|
128
|
-
"git_url": "https://api.github.com/repositories/123456789/git/blobs/def456",
|
|
129
|
-
"html_url": "https://github.com/owner/repo/blob/main/docs/example-file2.md",
|
|
130
|
-
"repository": {
|
|
131
|
-
"id": 123456789,
|
|
132
|
-
"name": "repo",
|
|
133
|
-
"full_name": "owner/repo",
|
|
134
|
-
"owner": {
|
|
135
|
-
"login": "owner",
|
|
136
|
-
"id": 12345,
|
|
137
|
-
"avatar_url": "https://avatars.githubusercontent.com/u/12345?v=4",
|
|
138
|
-
"url": "https://api.github.com/users/owner"
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
"text_matches": [
|
|
142
|
-
{
|
|
143
|
-
"object_url": "https://api.github.com/repositories/123456789/contents/docs/example-file2.md",
|
|
144
|
-
"object_type": "FileContent",
|
|
145
|
-
"property": "content",
|
|
146
|
-
"fragment": "This file also contains the search string.",
|
|
147
|
-
"matches": [
|
|
148
|
-
{
|
|
149
|
-
"text": "search string",
|
|
150
|
-
"indices": [25, 38]
|
|
151
|
-
}
|
|
152
|
-
]
|
|
153
|
-
}
|
|
154
|
-
]
|
|
155
|
-
}
|
|
156
|
-
]
|
|
157
|
-
}
|
|
158
|
-
*/
|
|
159
|
-
|
|
160
|
-
for (const item of searchResponse.data.items) {
|
|
161
|
-
// Check if text_matches exists and is not empty
|
|
162
|
-
if (!item.text_matches || item.text_matches.length === 0) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Loop through each text match. Can contain multiple fragments
|
|
167
|
-
for (const match of item.text_matches) {
|
|
168
|
-
// Split the fragment into lines, lines can be empty ('')
|
|
169
|
-
const lines = match.fragment.split("\n");
|
|
170
|
-
for (const line of lines) {
|
|
171
|
-
if (isLineWithDefinition(line)) {
|
|
172
|
-
// Generate a unique cache key for the file
|
|
173
|
-
const fileCacheKey = generateCacheKey('file', owner, repo, item.path);
|
|
174
|
-
const fileCacheFilePath = path.join(CACHE_DIR, `${fileCacheKey}.txt`);
|
|
175
|
-
|
|
176
|
-
let fileContent;
|
|
177
|
-
|
|
178
|
-
// Check if the file is already cached
|
|
179
|
-
if (fs.existsSync(fileCacheFilePath)) {
|
|
180
|
-
console.log(`Serving file from cache: ${fileCacheFilePath}`);
|
|
181
|
-
fileContent = fs.readFileSync(fileCacheFilePath, 'utf-8');
|
|
182
|
-
} else {
|
|
183
|
-
// Fetch file content from GitHub
|
|
184
|
-
console.log(`Downloading and caching file: ${fileCacheFilePath}`);
|
|
185
|
-
try {
|
|
186
|
-
const contentClient = await getContentClient(GITHUB_API_TOKEN);
|
|
187
|
-
const fileContentResponse = await contentClient.getContent(
|
|
188
|
-
item.repository.owner.login,
|
|
189
|
-
item.repository.name,
|
|
190
|
-
item.path
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
// Decode the file content (it's base64-encoded)
|
|
194
|
-
if (fileContentResponse.data.content) {
|
|
195
|
-
fileContent = Buffer.from(fileContentResponse.data.content, "base64").toString("utf-8");
|
|
196
|
-
// Save the file to the cache
|
|
197
|
-
fs.writeFileSync(fileCacheFilePath, fileContent, 'utf-8');
|
|
198
|
-
} else {
|
|
199
|
-
// If the file is larger than 1 MB, GitHub's API will return a download URL instead of the content.
|
|
200
|
-
console.log("File is too large. Download URL:", fileContentResponse.data.download_url);
|
|
201
|
-
fileContent = "";
|
|
202
|
-
}
|
|
203
|
-
} catch (error) {
|
|
204
|
-
console.error(`Error fetching content for ${item.path}:`, error);
|
|
205
|
-
fileContent = ""; // Set content to an empty string if there's an error
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Attach the content to the item
|
|
210
|
-
item.content = fileContent;
|
|
211
|
-
|
|
212
|
-
// Return the item as soon as we find the correct line
|
|
213
|
-
return item;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error("Error details:", {
|
|
220
|
-
message: error.message,
|
|
221
|
-
status: error.status,
|
|
222
|
-
type: error.constructor.name,
|
|
223
|
-
details: error.response?.data?.message || "No additional details"
|
|
224
|
-
});
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// If no item is found, return null or undefined
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
exports.fetchTermsFromGitHubRepository = fetchTermsFromGitHubRepository;
|
|
@@ -1,385 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
This test suite covers the main functionality of the fetchTermsFromGitHubRepository function including:
|
|
3
|
-
|
|
4
|
-
- Retrieving cached search results when available
|
|
5
|
-
- Fetching and caching new results when not in cache
|
|
6
|
-
- Handling API errors gracefully
|
|
7
|
-
- Processing empty search results correctly
|
|
8
|
-
|
|
9
|
-
The tests use Jest's mocking capabilities to avoid actual GitHub API calls and filesystem operations, making the tests fast and reliable.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const fs = require('fs');
|
|
13
|
-
const path = require('path');
|
|
14
|
-
|
|
15
|
-
// Mock dependencies
|
|
16
|
-
jest.mock('fs');
|
|
17
|
-
jest.mock('path');
|
|
18
|
-
jest.mock('crypto');
|
|
19
|
-
jest.mock('../utils/isLineWithDefinition');
|
|
20
|
-
jest.mock('../config/paths');
|
|
21
|
-
|
|
22
|
-
// Replace your static mock with a configurable one
|
|
23
|
-
jest.mock('./octokitClient', () => {
|
|
24
|
-
// Create mock functions that can be reconfigured in each test
|
|
25
|
-
const mockSearchClient = {
|
|
26
|
-
search: jest.fn()
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const mockContentClient = {
|
|
30
|
-
getContent: jest.fn()
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
return {
|
|
34
|
-
getSearchClient: jest.fn().mockResolvedValue(mockSearchClient),
|
|
35
|
-
getContentClient: jest.fn().mockResolvedValue(mockContentClient)
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Import the mocked module to access the mock functions
|
|
40
|
-
const octokitClient = require('./octokitClient');
|
|
41
|
-
|
|
42
|
-
// Import the function to test
|
|
43
|
-
const { fetchTermsFromGitHubRepository } = require('./fetchTermsFromGitHubRepository');
|
|
44
|
-
const { getPath } = require('../config/paths');
|
|
45
|
-
|
|
46
|
-
describe('fetchTermsFromGitHubRepository', () => {
|
|
47
|
-
// Setup common variables
|
|
48
|
-
const mockToken = 'mock-github-token';
|
|
49
|
-
const mockSearchString = 'test-search';
|
|
50
|
-
const mockOwner = 'test-owner';
|
|
51
|
-
const mockRepo = 'test-repo';
|
|
52
|
-
const mockSubdirectory = 'test-dir';
|
|
53
|
-
const mockCachePath = '/mock/cache/path';
|
|
54
|
-
|
|
55
|
-
const mockSearchResponse = {
|
|
56
|
-
data: {
|
|
57
|
-
total_count: 1,
|
|
58
|
-
items: [{
|
|
59
|
-
path: 'test-file.md',
|
|
60
|
-
repository: {
|
|
61
|
-
owner: { login: 'test-owner' },
|
|
62
|
-
name: 'test-repo'
|
|
63
|
-
},
|
|
64
|
-
text_matches: [{
|
|
65
|
-
fragment: '[[def: test-term]]\nContent'
|
|
66
|
-
}]
|
|
67
|
-
}]
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const mockFileContentResponse = {
|
|
72
|
-
data: {
|
|
73
|
-
content: Buffer.from('[[def: test-term]]\nTest file content').toString('base64')
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
beforeEach(() => {
|
|
78
|
-
// Reset all mocks
|
|
79
|
-
jest.clearAllMocks();
|
|
80
|
-
|
|
81
|
-
// Setup common mock returns
|
|
82
|
-
getPath.mockReturnValue(mockCachePath);
|
|
83
|
-
path.join.mockImplementation((...args) => args.join('/'));
|
|
84
|
-
|
|
85
|
-
// Mock crypto hash
|
|
86
|
-
const mockHash = {
|
|
87
|
-
update: jest.fn().mockReturnThis(),
|
|
88
|
-
digest: jest.fn().mockReturnValue('mock-hash')
|
|
89
|
-
};
|
|
90
|
-
require('crypto').createHash.mockReturnValue(mockHash);
|
|
91
|
-
|
|
92
|
-
// Default file system mocks
|
|
93
|
-
fs.existsSync.mockReturnValue(false);
|
|
94
|
-
fs.mkdirSync.mockReturnValue(undefined);
|
|
95
|
-
fs.writeFileSync.mockReturnValue(undefined);
|
|
96
|
-
fs.readFileSync.mockReturnValue('');
|
|
97
|
-
|
|
98
|
-
// Default isLineWithDefinition behavior
|
|
99
|
-
const { isLineWithDefinition } = require('../utils/isLineWithDefinition');
|
|
100
|
-
isLineWithDefinition.mockImplementation(line => {
|
|
101
|
-
return line.includes('[[def:') && line.includes(']]');
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('should return cached search results if available', async () => {
|
|
106
|
-
// Setup cache hit
|
|
107
|
-
fs.existsSync.mockReturnValue(true);
|
|
108
|
-
fs.readFileSync.mockReturnValue(JSON.stringify(mockSearchResponse));
|
|
109
|
-
|
|
110
|
-
// Also set up file content cache
|
|
111
|
-
fs.existsSync.mockImplementation((path) => {
|
|
112
|
-
return true; // Assume all files exist in cache
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Mock content response
|
|
116
|
-
fs.readFileSync.mockImplementation((path) => {
|
|
117
|
-
if (path.includes('.json')) {
|
|
118
|
-
return JSON.stringify(mockSearchResponse);
|
|
119
|
-
} else {
|
|
120
|
-
return '[[def: test-term]]\nContent';
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
125
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
126
|
-
);
|
|
127
|
-
|
|
128
|
-
expect(fs.existsSync).toHaveBeenCalled();
|
|
129
|
-
expect(fs.readFileSync).toHaveBeenCalled();
|
|
130
|
-
expect(result).not.toBeNull();
|
|
131
|
-
expect(result.path).toBe('test-file.md');
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
test('should fetch and cache search results if not cached', async () => {
|
|
135
|
-
// Set up mocks for API calls
|
|
136
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
137
|
-
mockSearchClient.search.mockResolvedValueOnce(mockSearchResponse);
|
|
138
|
-
|
|
139
|
-
const mockContentClient = await octokitClient.getContentClient();
|
|
140
|
-
mockContentClient.getContent.mockResolvedValueOnce({
|
|
141
|
-
data: {
|
|
142
|
-
content: Buffer.from('[[def: test-term]]\nContent').toString('base64')
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Mock file system for cache miss
|
|
147
|
-
fs.existsSync.mockReturnValue(false);
|
|
148
|
-
|
|
149
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
150
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
expect(fs.writeFileSync).toHaveBeenCalled();
|
|
154
|
-
expect(result).not.toBeNull();
|
|
155
|
-
expect(result.path).toBe('test-file.md');
|
|
156
|
-
expect(result.content).toBeDefined();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test('should handle API errors gracefully', async () => {
|
|
160
|
-
// Set up mock to throw an error
|
|
161
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
162
|
-
mockSearchClient.search.mockRejectedValueOnce(new Error('API error'));
|
|
163
|
-
|
|
164
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
165
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
expect(result).toBeNull();
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test('should handle empty search results', async () => {
|
|
172
|
-
// Set up mock for empty results
|
|
173
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
174
|
-
mockSearchClient.search.mockResolvedValueOnce({
|
|
175
|
-
data: {
|
|
176
|
-
total_count: 0,
|
|
177
|
-
items: []
|
|
178
|
-
}
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
182
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
expect(result).toBeNull();
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
test('should handle invalid GitHub tokens', async () => {
|
|
189
|
-
// Set up mock for authentication error
|
|
190
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
191
|
-
mockSearchClient.search.mockRejectedValueOnce({
|
|
192
|
-
status: 401,
|
|
193
|
-
message: 'Bad credentials'
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
197
|
-
'invalid-token', mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
198
|
-
);
|
|
199
|
-
|
|
200
|
-
expect(result).toBeNull();
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
test('should handle rate limiting errors', async () => {
|
|
204
|
-
// Set up mock for rate limit error
|
|
205
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
206
|
-
mockSearchClient.search.mockRejectedValueOnce({
|
|
207
|
-
status: 403,
|
|
208
|
-
message: 'API rate limit exceeded',
|
|
209
|
-
headers: {
|
|
210
|
-
'x-ratelimit-reset': (Date.now() / 1000 + 60).toString()
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
215
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
216
|
-
);
|
|
217
|
-
|
|
218
|
-
expect(result).toBeNull();
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
test('should handle large files that provide download URLs instead of content', async () => {
|
|
222
|
-
// Set up search response with large file
|
|
223
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
224
|
-
mockSearchClient.search.mockResolvedValueOnce({
|
|
225
|
-
data: {
|
|
226
|
-
total_count: 1,
|
|
227
|
-
items: [{
|
|
228
|
-
path: 'large-file.md',
|
|
229
|
-
repository: {
|
|
230
|
-
owner: { login: mockOwner },
|
|
231
|
-
name: mockRepo
|
|
232
|
-
},
|
|
233
|
-
text_matches: [{
|
|
234
|
-
fragment: '[[def: test-term]]\nContent'
|
|
235
|
-
}]
|
|
236
|
-
}]
|
|
237
|
-
}
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Set up content client for large file (no content)
|
|
241
|
-
const mockContentClient = await octokitClient.getContentClient();
|
|
242
|
-
mockContentClient.getContent.mockResolvedValueOnce({
|
|
243
|
-
data: {
|
|
244
|
-
content: null,
|
|
245
|
-
download_url: 'https://raw.githubusercontent.com/test-owner/test-repo/main/large-file.md'
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
250
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
251
|
-
);
|
|
252
|
-
|
|
253
|
-
expect(result).not.toBeNull();
|
|
254
|
-
expect(result.path).toBe('large-file.md');
|
|
255
|
-
expect(result.content).toBe("");
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test('should handle files with multiple definition lines', async () => {
|
|
259
|
-
// Set up search response with file containing multiple definitions
|
|
260
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
261
|
-
mockSearchClient.search.mockResolvedValueOnce({
|
|
262
|
-
data: {
|
|
263
|
-
total_count: 1,
|
|
264
|
-
items: [{
|
|
265
|
-
path: 'multi-def.md',
|
|
266
|
-
repository: {
|
|
267
|
-
owner: { login: mockOwner },
|
|
268
|
-
name: mockRepo
|
|
269
|
-
},
|
|
270
|
-
text_matches: [{
|
|
271
|
-
fragment: '[[def: term1]]\nContent'
|
|
272
|
-
}]
|
|
273
|
-
}]
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
// File content with multiple definitions
|
|
278
|
-
const fileContent = `
|
|
279
|
-
[[def: term1, alias1]]\n
|
|
280
|
-
This is definition 1\n
|
|
281
|
-
\n
|
|
282
|
-
[[def: term2, alias2]]\n
|
|
283
|
-
This is definition 2
|
|
284
|
-
`;
|
|
285
|
-
|
|
286
|
-
// Set up content client for multi-def file
|
|
287
|
-
const mockContentClient = await octokitClient.getContentClient();
|
|
288
|
-
mockContentClient.getContent.mockResolvedValueOnce({
|
|
289
|
-
data: {
|
|
290
|
-
content: Buffer.from(fileContent).toString('base64')
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
295
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
296
|
-
);
|
|
297
|
-
|
|
298
|
-
expect(result).not.toBeNull();
|
|
299
|
-
expect(result.path).toBe('multi-def.md');
|
|
300
|
-
expect(result.content).toContain('term1');
|
|
301
|
-
expect(result.content).toContain('term2');
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
test('should handle file content fetch errors', async () => {
|
|
305
|
-
// Set up search that succeeds
|
|
306
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
307
|
-
mockSearchClient.search.mockResolvedValueOnce({
|
|
308
|
-
data: {
|
|
309
|
-
total_count: 1,
|
|
310
|
-
items: [{
|
|
311
|
-
path: 'error-file.md',
|
|
312
|
-
repository: {
|
|
313
|
-
owner: { login: mockOwner },
|
|
314
|
-
name: mockRepo
|
|
315
|
-
},
|
|
316
|
-
text_matches: [{
|
|
317
|
-
fragment: '[[def: test-term]]\nContent'
|
|
318
|
-
}]
|
|
319
|
-
}]
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// Set up content fetch that fails
|
|
324
|
-
const mockContentClient = await octokitClient.getContentClient();
|
|
325
|
-
mockContentClient.getContent.mockRejectedValueOnce(new Error('File not found'));
|
|
326
|
-
|
|
327
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
328
|
-
mockToken, mockSearchString, mockOwner, mockRepo, mockSubdirectory
|
|
329
|
-
);
|
|
330
|
-
|
|
331
|
-
// This might be null or might return empty content based on implementation
|
|
332
|
-
if (result) {
|
|
333
|
-
expect(result.content).toBe("");
|
|
334
|
-
} else {
|
|
335
|
-
expect(result).toBeNull();
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
test('should correctly filter by subdirectory', async () => {
|
|
340
|
-
// Set up search with multiple results in different directories
|
|
341
|
-
const mockSearchClient = await octokitClient.getSearchClient();
|
|
342
|
-
mockSearchClient.search.mockResolvedValueOnce({
|
|
343
|
-
data: {
|
|
344
|
-
total_count: 2,
|
|
345
|
-
items: [
|
|
346
|
-
{
|
|
347
|
-
path: 'test-dir/file1.md',
|
|
348
|
-
repository: {
|
|
349
|
-
owner: { login: mockOwner },
|
|
350
|
-
name: mockRepo
|
|
351
|
-
},
|
|
352
|
-
text_matches: [{
|
|
353
|
-
fragment: '[[def: term1]]\nContent'
|
|
354
|
-
}]
|
|
355
|
-
},
|
|
356
|
-
{
|
|
357
|
-
path: 'other-dir/file2.md',
|
|
358
|
-
repository: {
|
|
359
|
-
owner: { login: mockOwner },
|
|
360
|
-
name: mockRepo
|
|
361
|
-
},
|
|
362
|
-
text_matches: [{
|
|
363
|
-
fragment: '[[def: term2]]\nContent'
|
|
364
|
-
}]
|
|
365
|
-
}
|
|
366
|
-
]
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
// Set up content client
|
|
371
|
-
const mockContentClient = await octokitClient.getContentClient();
|
|
372
|
-
mockContentClient.getContent.mockResolvedValueOnce({
|
|
373
|
-
data: {
|
|
374
|
-
content: Buffer.from('[[def: term1]]\nContent').toString('base64')
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
const result = await fetchTermsFromGitHubRepository(
|
|
379
|
-
mockToken, mockSearchString, mockOwner, mockRepo, 'test-dir'
|
|
380
|
-
);
|
|
381
|
-
|
|
382
|
-
expect(result).not.toBeNull();
|
|
383
|
-
expect(result.path).toBe('test-dir/file1.md');
|
|
384
|
-
});
|
|
385
|
-
});
|