zotero-ts-api
Version:
A TypeScript API wrapper for Zotero
346 lines • 16.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const Library_1 = require("./Library");
const Item_1 = require("./Item");
const ZCollection_1 = require("./ZCollection");
const test_helpers_1 = require("../__tests__/utils/test-helpers");
(0, vitest_1.describe)('Library', () => {
let library;
const validApiKey = 'test-api-key-123';
const validUserId = 'user123';
const validGroupId = 'group456';
(0, vitest_1.beforeEach)(() => {
library = new Library_1.Library(validApiKey, validUserId, 'users');
});
(0, vitest_1.describe)('Constructor', () => {
(0, vitest_1.it)('should create library with valid parameters', () => {
(0, vitest_1.expect)(library.apiKeyValue).toBe(validApiKey);
});
(0, vitest_1.it)('should create user library', () => {
const userLibrary = new Library_1.Library(validApiKey, validUserId, 'users');
(0, vitest_1.expect)(userLibrary.apiKeyValue).toBe(validApiKey);
});
(0, vitest_1.it)('should create group library', () => {
const groupLibrary = new Library_1.Library(validApiKey, validGroupId, 'groups');
(0, vitest_1.expect)(groupLibrary.apiKeyValue).toBe(validApiKey);
});
(0, vitest_1.it)('should throw error with empty API key', () => {
(0, vitest_1.expect)(() => new Library_1.Library('', validUserId, 'users'))
.toThrow('API key is required');
});
(0, vitest_1.it)('should throw error with whitespace API key', () => {
(0, vitest_1.expect)(() => new Library_1.Library(' ', validUserId, 'users'))
.toThrow('API key is required');
});
(0, vitest_1.it)('should throw error with null API key', () => {
(0, vitest_1.expect)(() => new Library_1.Library(null, validUserId, 'users'))
.toThrow('API key is required');
});
(0, vitest_1.it)('should throw error with undefined API key', () => {
(0, vitest_1.expect)(() => new Library_1.Library(undefined, validUserId, 'users'))
.toThrow('API key is required');
});
(0, vitest_1.it)('should throw error with empty library ID', () => {
(0, vitest_1.expect)(() => new Library_1.Library(validApiKey, '', 'users'))
.toThrow('Library ID is required');
});
(0, vitest_1.it)('should throw error with whitespace library ID', () => {
(0, vitest_1.expect)(() => new Library_1.Library(validApiKey, ' ', 'users'))
.toThrow('Library ID is required');
});
});
(0, vitest_1.describe)('Getters before connection', () => {
(0, vitest_1.it)('should return undefined for name before connection', () => {
(0, vitest_1.expect)(library.name).toBeUndefined();
});
(0, vitest_1.it)('should return undefined for id before connection', () => {
(0, vitest_1.expect)(library.id).toBeUndefined();
});
(0, vitest_1.it)('should return undefined for type before connection', () => {
(0, vitest_1.expect)(library.type).toBeUndefined();
});
});
(0, vitest_1.describe)('connect()', () => {
(0, vitest_1.it)('should connect successfully with valid credentials', async () => {
const mockLibraryData = {
id: 123,
name: 'Test Library',
type: 'user',
description: 'Test description'
};
(0, test_helpers_1.mockFetchSuccess)(mockLibraryData);
await library.connect();
(0, vitest_1.expect)(library.name).toBe('Test Library');
(0, vitest_1.expect)(library.id).toBe(123);
(0, vitest_1.expect)(library.type).toBe('user');
});
(0, vitest_1.it)('should handle 403 Forbidden error', async () => {
(0, test_helpers_1.mockFetchError)(403, 'Forbidden');
await (0, vitest_1.expect)(library.connect())
.rejects.toThrow('Failed to connect to Zotero API (403): Forbidden');
});
(0, vitest_1.it)('should handle malformed JSON response', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: vitest_1.vi.fn().mockRejectedValue(new Error('Invalid JSON'))
});
await (0, vitest_1.expect)(library.connect())
.rejects.toThrow('Connection failed');
});
});
(0, vitest_1.describe)('getCollections()', () => {
(0, vitest_1.it)('should retrieve collections successfully', async () => {
const mockCollections = [
{ key: 'ABC123', name: 'Collection 1', parentCollection: undefined, version: 1 },
{ key: 'DEF456', name: 'Collection 2', parentCollection: 'ABC123', version: 1 }
];
vitest_1.vi.mocked(fetch).mockResolvedValueOnce((0, test_helpers_1.createMockResponse)(mockCollections));
const collections = await library.getCollections();
(0, vitest_1.expect)(collections).toHaveLength(2);
(0, vitest_1.expect)(collections[0]).toBeInstanceOf(ZCollection_1.ZCollection);
(0, vitest_1.expect)(collections[0].key).toBe('ABC123');
(0, vitest_1.expect)(collections[1].key).toBe('DEF456');
});
(0, vitest_1.it)('should handle empty collections response', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce((0, test_helpers_1.createMockResponse)([]));
const collections = await library.getCollections();
(0, vitest_1.expect)(collections).toEqual([]);
});
(0, vitest_1.it)('should handle API error when fetching collections', async () => {
(0, test_helpers_1.mockFetchError)(500, 'Internal Server Error');
await (0, vitest_1.expect)(library.getCollections())
.rejects.toThrow('API request failed (500): Internal Server Error');
});
});
(0, vitest_1.describe)('getAllItems()', () => {
(0, vitest_1.it)('should retrieve items successfully', async () => {
const mockItems = [
{
data: {
key: 'ITEM123',
title: 'Test Article',
itemType: 'journalArticle',
creators: [],
tags: [],
collections: [],
version: 1,
relations: {}
}
},
{
data: {
key: 'ITEM456',
title: 'Another Article',
itemType: 'webpage',
creators: [],
tags: [],
collections: [],
version: 2,
relations: {}
}
}
];
vitest_1.vi.mocked(fetch).mockResolvedValueOnce((0, test_helpers_1.createMockResponse)(mockItems));
const items = await library.getAllItems();
(0, vitest_1.expect)(items).toHaveLength(2);
(0, vitest_1.expect)(items[0]).toBeInstanceOf(Item_1.Item);
(0, vitest_1.expect)(items[0].key).toBe('ITEM123');
(0, vitest_1.expect)(items[0].title).toBe('Test Article');
(0, vitest_1.expect)(items[1].title).toBe('Another Article');
});
(0, vitest_1.it)('should handle empty items response', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce((0, test_helpers_1.createMockResponse)([]));
const items = await library.getAllItems();
(0, vitest_1.expect)(items).toEqual([]);
});
});
(0, vitest_1.describe)('createCollection()', () => {
(0, vitest_1.it)('should create collection with valid name', async () => {
const mockResponse = {
key: 'NEW123',
name: 'New Collection',
parentCollection: undefined
};
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => [mockResponse]
});
const collection = await library.createCollection('New Collection');
(0, vitest_1.expect)(collection).toBeInstanceOf(ZCollection_1.ZCollection);
(0, vitest_1.expect)(collection.name).toBe('New Collection');
(0, vitest_1.expect)(fetch).toHaveBeenCalledWith('https://api.zotero.org/users/user123/collections', vitest_1.expect.objectContaining({
method: 'POST',
body: JSON.stringify([{ name: 'New Collection', parentCollection: undefined }])
}));
});
(0, vitest_1.it)('should create collection with parent', async () => {
const mockResponse = {
key: 'CHILD123',
name: 'Child Collection',
parentCollection: 'PARENT123'
};
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => [mockResponse]
});
const collection = await library.createCollection('Child Collection', 'PARENT123');
(0, vitest_1.expect)(collection.name).toBe('Child Collection');
(0, vitest_1.expect)(collection.parentCollection).toBe('PARENT123');
});
(0, vitest_1.it)('should throw error with empty collection name', async () => {
await (0, vitest_1.expect)(library.createCollection(''))
.rejects.toThrow('Collection name is required');
});
(0, vitest_1.it)('should throw error with whitespace collection name', async () => {
await (0, vitest_1.expect)(library.createCollection(' '))
.rejects.toThrow('Collection name is required');
});
(0, vitest_1.it)('should throw error with null collection name', async () => {
await (0, vitest_1.expect)(library.createCollection(null))
.rejects.toThrow('Collection name is required');
});
(0, vitest_1.it)('should handle API error during collection creation', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 400,
statusText: 'Bad Request'
});
await (0, vitest_1.expect)(library.createCollection('Test Collection'))
.rejects.toThrow('API request failed (400): Bad Request');
});
});
(0, vitest_1.describe)('createItem()', () => {
(0, vitest_1.it)('should create item with valid data', async () => {
const itemData = {
title: 'Test Article',
itemType: 'journalArticle',
url: 'https://example.com'
};
const mockResponse = {
successful: {
'0': {
data: {
...itemData,
key: 'NEWITEM123',
version: 1,
creators: [],
tags: [],
collections: []
}
}
}
};
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const item = await library.createItem(itemData);
(0, vitest_1.expect)(item).toBeInstanceOf(Item_1.Item);
(0, vitest_1.expect)(item.title).toBe('Test Article');
(0, vitest_1.expect)(item.key).toBe('NEWITEM123');
});
(0, vitest_1.it)('should create item with default itemType when not provided', async () => {
const itemData = { title: 'Test Item' };
const mockResponse = [{
data: {
title: 'Test Item',
itemType: 'webpage',
key: 'NEWITEM456',
version: 1,
creators: [],
tags: [],
collections: []
}
}];
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const item = await library.createItem(itemData);
(0, vitest_1.expect)(item.itemType).toBe('webpage');
});
(0, vitest_1.it)('should filter unknown fields and warn', async () => {
const itemData = {
title: 'Test Article',
unknownField: 'should be filtered',
anotherUnknown: 123
};
const mockResponse = [{
data: {
title: 'Test Article',
itemType: 'webpage',
key: 'FILTERED123',
version: 1,
creators: [],
tags: [],
collections: []
}
}];
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockResponse
});
const consoleSpy = vitest_1.vi.spyOn(console, 'warn');
await library.createItem(itemData);
(0, vitest_1.expect)(consoleSpy).toHaveBeenCalledWith('[Zotero] Unknown fields ignored: unknownField, anotherUnknown');
});
(0, vitest_1.it)('should throw error when title is missing', async () => {
const itemData = { itemType: 'journalArticle' };
await (0, vitest_1.expect)(library.createItem(itemData))
.rejects.toThrow('A title is required to create a Zotero item');
});
(0, vitest_1.it)('should throw error when title is empty', async () => {
const itemData = { title: '', itemType: 'journalArticle' };
await (0, vitest_1.expect)(library.createItem(itemData))
.rejects.toThrow('A title is required to create a Zotero item');
});
(0, vitest_1.it)('should throw error when title is whitespace', async () => {
const itemData = { title: ' ', itemType: 'journalArticle' };
await (0, vitest_1.expect)(library.createItem(itemData))
.rejects.toThrow('A title is required to create a Zotero item');
});
(0, vitest_1.it)('should handle malformed API response', async () => {
const itemData = { title: 'Test Article' };
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => ({ unexpected: 'format' })
});
await (0, vitest_1.expect)(library.createItem(itemData))
.rejects.toThrow('Zotero API did not return a valid created item');
});
});
(0, vitest_1.describe)('getTags()', () => {
(0, vitest_1.it)('should retrieve tags successfully', async () => {
const mockTags = [
{ tag: 'science' },
{ tag: 'research' },
{ tag: 'typescript' }
];
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => mockTags
});
const tags = await library.getTags();
(0, vitest_1.expect)(tags).toEqual(['science', 'research', 'typescript']);
});
(0, vitest_1.it)('should handle empty tags response', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: true,
json: async () => []
});
const tags = await library.getTags();
(0, vitest_1.expect)(tags).toEqual([]);
});
(0, vitest_1.it)('should handle API error when fetching tags', async () => {
vitest_1.vi.mocked(fetch).mockResolvedValueOnce({
ok: false,
status: 503,
statusText: 'Service Unavailable'
});
await (0, vitest_1.expect)(library.getTags())
.rejects.toThrow('API request failed (503): Service Unavailable');
});
});
});
//# sourceMappingURL=Library.test.js.map