arxiv-api-wrapper 1.1.0 → 1.1.1
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/README.md +250 -250
- package/package.json +5 -3
- package/src/arxivAPIRead.ts +316 -316
- package/src/atom.ts +1 -1
- package/src/index.ts +57 -57
- package/src/types.ts +265 -265
- package/tests/arxivAPI.integration.test.ts +144 -144
- package/tests/arxivAPIRead.test.ts +1 -1
- package/tests/fixtures/parseEntries/2507.17541.json.ts +1 -1
- package/tests/fixtures/parseEntries/search_agdur.json.ts +1 -1
- package/tsconfig.json +13 -0
|
@@ -1,144 +1,144 @@
|
|
|
1
|
-
import { describe, it, test, expect } from 'vitest';
|
|
2
|
-
import { getArxivEntries, getArxivEntriesById } from '../src/arxivAPIRead';
|
|
3
|
-
|
|
4
|
-
// Integration tests that make real HTTP calls to arXiv API.
|
|
5
|
-
// These are intentionally conservative in request size and rate.
|
|
6
|
-
|
|
7
|
-
describe('arXiv API integration', () => {
|
|
8
|
-
test('fetches results by search_query and then by id_list', async () => {
|
|
9
|
-
console.log('Starting first API call (search query)...');
|
|
10
|
-
let first;
|
|
11
|
-
try {
|
|
12
|
-
first = await getArxivEntries({
|
|
13
|
-
search: {
|
|
14
|
-
title: ['overlapping'],
|
|
15
|
-
author: ['Vilhelm Agdur'],
|
|
16
|
-
},
|
|
17
|
-
start: 0,
|
|
18
|
-
maxResults: 1,
|
|
19
|
-
sortBy: 'submittedDate',
|
|
20
|
-
sortOrder: 'descending',
|
|
21
|
-
timeoutMs: 15000,
|
|
22
|
-
retries: 2,
|
|
23
|
-
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
24
|
-
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
25
|
-
});
|
|
26
|
-
console.log('First API call completed successfully');
|
|
27
|
-
} catch (error) {
|
|
28
|
-
console.error('First API call failed:', error);
|
|
29
|
-
throw new Error(`Failed to fetch search results: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
expect(first.feed).toBeTruthy();
|
|
33
|
-
expect(typeof first.feed.totalResults).toBe('number');
|
|
34
|
-
expect(Array.isArray(first.entries)).toBe(true);
|
|
35
|
-
expect(first.entries.length).toBeGreaterThanOrEqual(0);
|
|
36
|
-
|
|
37
|
-
if (first.entries.length === 0) {
|
|
38
|
-
const responseDetails = {
|
|
39
|
-
feed: first.feed,
|
|
40
|
-
totalResults: first.feed?.totalResults,
|
|
41
|
-
entriesCount: first.entries.length,
|
|
42
|
-
entries: first.entries,
|
|
43
|
-
};
|
|
44
|
-
console.error('No entries returned from search query. Response details:', JSON.stringify(responseDetails, null, 2));
|
|
45
|
-
throw new Error(
|
|
46
|
-
`Search query (title: "overlapping", author: "Vilhelm Agdur") returned no entries. ` +
|
|
47
|
-
`Feed metadata: totalResults=${first.feed?.totalResults}, ` +
|
|
48
|
-
`entries array length=${first.entries.length}. ` +
|
|
49
|
-
`This indicates the API call succeeded but returned no results, which is unexpected.`
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Verify the first result matches the search criteria
|
|
54
|
-
const firstEntry = first.entries[0];
|
|
55
|
-
|
|
56
|
-
// Check that the title contains "overlapping" (case-insensitive)
|
|
57
|
-
const titleLower = firstEntry.title.toLowerCase();
|
|
58
|
-
expect(titleLower).toContain('overlapping');
|
|
59
|
-
|
|
60
|
-
// Check that at least one author is "Vilhelm Agdur"
|
|
61
|
-
const authorNames = firstEntry.authors.map(a => a.name);
|
|
62
|
-
const hasVilhelmAgdur = authorNames.some(name =>
|
|
63
|
-
name.toLowerCase().includes('vilhelm') && name.toLowerCase().includes('agdur')
|
|
64
|
-
);
|
|
65
|
-
expect(hasVilhelmAgdur).toBe(true);
|
|
66
|
-
|
|
67
|
-
// Log the actual result for debugging if needed
|
|
68
|
-
console.log(`Verified result: title="${firstEntry.title}", authors=[${authorNames.join(', ')}]`);
|
|
69
|
-
|
|
70
|
-
const arxivId = firstEntry.arxivId;
|
|
71
|
-
if (!arxivId) {
|
|
72
|
-
console.log('No arxivId found in first entry, skipping id_list test');
|
|
73
|
-
return; // Skip id_list fetch if id is unavailable
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log(`Starting second API call (id_list) for arxivId: ${arxivId}`);
|
|
77
|
-
let second;
|
|
78
|
-
try {
|
|
79
|
-
second = await getArxivEntries({
|
|
80
|
-
idList: [arxivId],
|
|
81
|
-
timeoutMs: 15000,
|
|
82
|
-
retries: 2,
|
|
83
|
-
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
84
|
-
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
85
|
-
});
|
|
86
|
-
console.log('Second API call completed successfully');
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error('Second API call failed:', error);
|
|
89
|
-
throw new Error(`Failed to fetch entry by id_list: ${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
expect(second.entries.length).toBeGreaterThanOrEqual(1);
|
|
93
|
-
expect(second.entries[0].arxivId).toBe(arxivId);
|
|
94
|
-
expect(second.entries[0].title.length).toBeGreaterThan(0);
|
|
95
|
-
expect(second.entries[0].links.length).toBeGreaterThanOrEqual(1);
|
|
96
|
-
}, 120000); // Increased to 120 seconds to account for rate limiting, retries, and backoff delays
|
|
97
|
-
|
|
98
|
-
test('fetches papers by ID using getArxivEntriesById', async () => {
|
|
99
|
-
// Use a well-known arXiv paper ID for testing
|
|
100
|
-
const testIds = ['2101.01234', '2101.05678'];
|
|
101
|
-
|
|
102
|
-
console.log(`Starting API call with getArxivEntriesById for IDs: ${testIds.join(', ')}`);
|
|
103
|
-
let result;
|
|
104
|
-
try {
|
|
105
|
-
result = await getArxivEntriesById(testIds, {
|
|
106
|
-
timeoutMs: 15000,
|
|
107
|
-
retries: 2,
|
|
108
|
-
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
109
|
-
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
110
|
-
});
|
|
111
|
-
console.log('API call completed successfully');
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('API call failed:', error);
|
|
114
|
-
throw new Error(`Failed to fetch entries by ID: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
expect(result.feed).toBeTruthy();
|
|
118
|
-
expect(typeof result.feed.totalResults).toBe('number');
|
|
119
|
-
expect(Array.isArray(result.entries)).toBe(true);
|
|
120
|
-
expect(result.entries.length).toBeGreaterThanOrEqual(0);
|
|
121
|
-
|
|
122
|
-
// Verify that we got results for at least some of the requested IDs
|
|
123
|
-
if (result.entries.length > 0) {
|
|
124
|
-
const returnedIds = result.entries.map(e => e.arxivId.split('v')[0]); // Remove version suffix for comparison
|
|
125
|
-
const requestedIds = testIds.map(id => id.split('v')[0]);
|
|
126
|
-
|
|
127
|
-
// At least one requested ID should be in the results
|
|
128
|
-
const hasMatchingId = requestedIds.some(reqId =>
|
|
129
|
-
returnedIds.some(retId => retId === reqId || retId.startsWith(reqId))
|
|
130
|
-
);
|
|
131
|
-
expect(hasMatchingId).toBe(true);
|
|
132
|
-
|
|
133
|
-
// Verify entry structure
|
|
134
|
-
const firstEntry = result.entries[0];
|
|
135
|
-
expect(firstEntry.arxivId).toBeTruthy();
|
|
136
|
-
expect(firstEntry.title).toBeTruthy();
|
|
137
|
-
expect(firstEntry.title.length).toBeGreaterThan(0);
|
|
138
|
-
expect(Array.isArray(firstEntry.authors)).toBe(true);
|
|
139
|
-
expect(Array.isArray(firstEntry.links)).toBe(true);
|
|
140
|
-
expect(firstEntry.links.length).toBeGreaterThanOrEqual(1);
|
|
141
|
-
}
|
|
142
|
-
}, 120000);
|
|
143
|
-
});
|
|
144
|
-
|
|
1
|
+
import { describe, it, test, expect } from 'vitest';
|
|
2
|
+
import { getArxivEntries, getArxivEntriesById } from '../src/arxivAPIRead.js';
|
|
3
|
+
|
|
4
|
+
// Integration tests that make real HTTP calls to arXiv API.
|
|
5
|
+
// These are intentionally conservative in request size and rate.
|
|
6
|
+
|
|
7
|
+
describe('arXiv API integration', () => {
|
|
8
|
+
test('fetches results by search_query and then by id_list', async () => {
|
|
9
|
+
console.log('Starting first API call (search query)...');
|
|
10
|
+
let first;
|
|
11
|
+
try {
|
|
12
|
+
first = await getArxivEntries({
|
|
13
|
+
search: {
|
|
14
|
+
title: ['overlapping'],
|
|
15
|
+
author: ['Vilhelm Agdur'],
|
|
16
|
+
},
|
|
17
|
+
start: 0,
|
|
18
|
+
maxResults: 1,
|
|
19
|
+
sortBy: 'submittedDate',
|
|
20
|
+
sortOrder: 'descending',
|
|
21
|
+
timeoutMs: 15000,
|
|
22
|
+
retries: 2,
|
|
23
|
+
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
24
|
+
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
25
|
+
});
|
|
26
|
+
console.log('First API call completed successfully');
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('First API call failed:', error);
|
|
29
|
+
throw new Error(`Failed to fetch search results: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
expect(first.feed).toBeTruthy();
|
|
33
|
+
expect(typeof first.feed.totalResults).toBe('number');
|
|
34
|
+
expect(Array.isArray(first.entries)).toBe(true);
|
|
35
|
+
expect(first.entries.length).toBeGreaterThanOrEqual(0);
|
|
36
|
+
|
|
37
|
+
if (first.entries.length === 0) {
|
|
38
|
+
const responseDetails = {
|
|
39
|
+
feed: first.feed,
|
|
40
|
+
totalResults: first.feed?.totalResults,
|
|
41
|
+
entriesCount: first.entries.length,
|
|
42
|
+
entries: first.entries,
|
|
43
|
+
};
|
|
44
|
+
console.error('No entries returned from search query. Response details:', JSON.stringify(responseDetails, null, 2));
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Search query (title: "overlapping", author: "Vilhelm Agdur") returned no entries. ` +
|
|
47
|
+
`Feed metadata: totalResults=${first.feed?.totalResults}, ` +
|
|
48
|
+
`entries array length=${first.entries.length}. ` +
|
|
49
|
+
`This indicates the API call succeeded but returned no results, which is unexpected.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Verify the first result matches the search criteria
|
|
54
|
+
const firstEntry = first.entries[0];
|
|
55
|
+
|
|
56
|
+
// Check that the title contains "overlapping" (case-insensitive)
|
|
57
|
+
const titleLower = firstEntry.title.toLowerCase();
|
|
58
|
+
expect(titleLower).toContain('overlapping');
|
|
59
|
+
|
|
60
|
+
// Check that at least one author is "Vilhelm Agdur"
|
|
61
|
+
const authorNames = firstEntry.authors.map((a: { name: string }) => a.name);
|
|
62
|
+
const hasVilhelmAgdur = authorNames.some((name: string) =>
|
|
63
|
+
name.toLowerCase().includes('vilhelm') && name.toLowerCase().includes('agdur')
|
|
64
|
+
);
|
|
65
|
+
expect(hasVilhelmAgdur).toBe(true);
|
|
66
|
+
|
|
67
|
+
// Log the actual result for debugging if needed
|
|
68
|
+
console.log(`Verified result: title="${firstEntry.title}", authors=[${authorNames.join(', ')}]`);
|
|
69
|
+
|
|
70
|
+
const arxivId = firstEntry.arxivId;
|
|
71
|
+
if (!arxivId) {
|
|
72
|
+
console.log('No arxivId found in first entry, skipping id_list test');
|
|
73
|
+
return; // Skip id_list fetch if id is unavailable
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`Starting second API call (id_list) for arxivId: ${arxivId}`);
|
|
77
|
+
let second;
|
|
78
|
+
try {
|
|
79
|
+
second = await getArxivEntries({
|
|
80
|
+
idList: [arxivId],
|
|
81
|
+
timeoutMs: 15000,
|
|
82
|
+
retries: 2,
|
|
83
|
+
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
84
|
+
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
85
|
+
});
|
|
86
|
+
console.log('Second API call completed successfully');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error('Second API call failed:', error);
|
|
89
|
+
throw new Error(`Failed to fetch entry by id_list: ${error instanceof Error ? error.message : String(error)}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
expect(second.entries.length).toBeGreaterThanOrEqual(1);
|
|
93
|
+
expect(second.entries[0].arxivId).toBe(arxivId);
|
|
94
|
+
expect(second.entries[0].title.length).toBeGreaterThan(0);
|
|
95
|
+
expect(second.entries[0].links.length).toBeGreaterThanOrEqual(1);
|
|
96
|
+
}, 120000); // Increased to 120 seconds to account for rate limiting, retries, and backoff delays
|
|
97
|
+
|
|
98
|
+
test('fetches papers by ID using getArxivEntriesById', async () => {
|
|
99
|
+
// Use a well-known arXiv paper ID for testing
|
|
100
|
+
const testIds = ['2101.01234', '2101.05678'];
|
|
101
|
+
|
|
102
|
+
console.log(`Starting API call with getArxivEntriesById for IDs: ${testIds.join(', ')}`);
|
|
103
|
+
let result;
|
|
104
|
+
try {
|
|
105
|
+
result = await getArxivEntriesById(testIds, {
|
|
106
|
+
timeoutMs: 15000,
|
|
107
|
+
retries: 2,
|
|
108
|
+
rateLimit: { tokensPerInterval: 1, intervalMs: 1000 },
|
|
109
|
+
userAgent: 'arxiv-api-wrapper-tests/1.0',
|
|
110
|
+
});
|
|
111
|
+
console.log('API call completed successfully');
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error('API call failed:', error);
|
|
114
|
+
throw new Error(`Failed to fetch entries by ID: ${error instanceof Error ? error.message : String(error)}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
expect(result.feed).toBeTruthy();
|
|
118
|
+
expect(typeof result.feed.totalResults).toBe('number');
|
|
119
|
+
expect(Array.isArray(result.entries)).toBe(true);
|
|
120
|
+
expect(result.entries.length).toBeGreaterThanOrEqual(0);
|
|
121
|
+
|
|
122
|
+
// Verify that we got results for at least some of the requested IDs
|
|
123
|
+
if (result.entries.length > 0) {
|
|
124
|
+
const returnedIds = result.entries.map((e: { arxivId: string }) => e.arxivId.split('v')[0]); // Remove version suffix for comparison
|
|
125
|
+
const requestedIds = testIds.map((id: string) => id.split('v')[0]);
|
|
126
|
+
|
|
127
|
+
// At least one requested ID should be in the results
|
|
128
|
+
const hasMatchingId = requestedIds.some((reqId: string) =>
|
|
129
|
+
returnedIds.some((retId: string) => retId === reqId || retId.startsWith(reqId))
|
|
130
|
+
);
|
|
131
|
+
expect(hasMatchingId).toBe(true);
|
|
132
|
+
|
|
133
|
+
// Verify entry structure
|
|
134
|
+
const firstEntry = result.entries[0];
|
|
135
|
+
expect(firstEntry.arxivId).toBeTruthy();
|
|
136
|
+
expect(firstEntry.title).toBeTruthy();
|
|
137
|
+
expect(firstEntry.title.length).toBeGreaterThan(0);
|
|
138
|
+
expect(Array.isArray(firstEntry.authors)).toBe(true);
|
|
139
|
+
expect(Array.isArray(firstEntry.links)).toBe(true);
|
|
140
|
+
expect(firstEntry.links.length).toBeGreaterThanOrEqual(1);
|
|
141
|
+
}
|
|
142
|
+
}, 120000);
|
|
143
|
+
});
|
|
144
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Basic tests for query building logic using Vitest
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
|
-
import { buildSearchQuery } from '../src/arxivAPIRead';
|
|
3
|
+
import { buildSearchQuery } from '../src/arxivAPIRead.js';
|
|
4
4
|
|
|
5
5
|
describe('buildSearchQuery', () => {
|
|
6
6
|
it('ANDs top-level fields', () => {
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"resolveJsonModule": true
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*", "tests/**/*"],
|
|
12
|
+
"exclude": ["node_modules", "docs"]
|
|
13
|
+
}
|