mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
fix(storygraph): preserve reading attempt start date (#687)
* fix(storygraph): preserve reading attempt start date Skip redundant active-reading status updates before syncing progress so StoryGraph keeps the existing reading attempt and start date. Fixes #667 * test(storygraph): cover active status equivalence Exercise exact and cross-mapped active reading statuses so patch coverage verifies every start-date preservation path. * test(storygraph): complete active status matrix Cover the exact rereading status pair and retain literal tuple types for every active-reading equivalence case. --------- Co-authored-by: chooh2 <chooh2@users.noreply.github.com> Co-authored-by: neon <18559658+neonsolstice@users.noreply.github.com>
This commit is contained in:
@@ -229,6 +229,41 @@ describe('StorygraphSyncService', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['rereading', 'reading'],
|
||||
['currently reading', 'reading'],
|
||||
['currently reading', 'rereading'],
|
||||
['rereading', 'rereading'],
|
||||
] as const)('updates progress without resetting a %s StoryGraph attempt for local status %s', async (currentStatus, localStatus) => {
|
||||
mockSettingsService.getCookiesForUser.mockResolvedValue(cookies);
|
||||
mockRepo.findSyncableBook.mockResolvedValue({ ...readingBook, status: localStatus });
|
||||
mockRepo.findBookState.mockResolvedValue({
|
||||
storygraphBookId: 'abc-123',
|
||||
matchMethod: 'isbn',
|
||||
lastSyncedAt: new Date('2026-07-01T00:00:00Z'),
|
||||
lastSyncedStatus: localStatus,
|
||||
lastSyncedProgress: 10,
|
||||
});
|
||||
mockMatchService.matchBook.mockResolvedValue({ storygraphBookId: 'abc-123', matchMethod: 'isbn' });
|
||||
mockClient.get.mockResolvedValue({
|
||||
status: 200,
|
||||
html: `<button class="read-status-label">${currentStatus}</button><input name="read_status[book_num_of_pages]" value="288">`,
|
||||
redirectedToSignIn: false,
|
||||
});
|
||||
|
||||
await expect(makeService().syncBook(1, 1)).resolves.toBe('synced');
|
||||
|
||||
expect(mockClient.post).toHaveBeenCalledTimes(1);
|
||||
expect(mockClient.post).toHaveBeenCalledWith(
|
||||
1,
|
||||
cookies,
|
||||
'/update-progress',
|
||||
expect.objectContaining({ 'read_status[progress_number]': '42', book_id: 'abc-123' }),
|
||||
'csrf-token',
|
||||
);
|
||||
expect(mockClient.post).not.toHaveBeenCalledWith(1, cookies, expect.stringContaining('/update-status.js'), expect.anything(), 'csrf-token');
|
||||
});
|
||||
|
||||
it('records a failure when StoryGraph rejects the progress update', async () => {
|
||||
mockSettingsService.getCookiesForUser.mockResolvedValue(cookies);
|
||||
mockRepo.findSyncableBook.mockResolvedValue(readingBook);
|
||||
|
||||
@@ -522,7 +522,9 @@ export class StorygraphSyncService {
|
||||
}
|
||||
|
||||
private async updateStatus(userId: number, cookies: StorygraphCookies, storygraphBookId: string, status: string): Promise<void> {
|
||||
const { csrf } = await this.fetchBookPage(userId, cookies, storygraphBookId);
|
||||
const { html, csrf } = await this.fetchBookPage(userId, cookies, storygraphBookId);
|
||||
const currentStatus = this.extractCurrentStatus(html);
|
||||
if (this.statusesMatch(currentStatus, status)) return;
|
||||
|
||||
const response = await this.client.post(userId, cookies, `/update-status.js?book_id=${storygraphBookId}&status=${status}`, {}, csrf);
|
||||
|
||||
@@ -543,6 +545,20 @@ export class StorygraphSyncService {
|
||||
throw new Error(`status_update_failed:${response.status}`);
|
||||
}
|
||||
|
||||
private extractCurrentStatus(html: string): string | null {
|
||||
const $ = cheerio.load(html);
|
||||
const label = $('.read-status-label').first().text().trim();
|
||||
return label ? label.toLowerCase().replace(/\s+/g, '-') : null;
|
||||
}
|
||||
|
||||
private statusesMatch(currentStatus: string | null, requestedStatus: string): boolean {
|
||||
if (currentStatus === requestedStatus) return true;
|
||||
return (
|
||||
(currentStatus === STORYGRAPH_STATUS.CURRENTLY_READING || currentStatus === STORYGRAPH_STATUS.REREADING) &&
|
||||
(requestedStatus === STORYGRAPH_STATUS.CURRENTLY_READING || requestedStatus === STORYGRAPH_STATUS.REREADING)
|
||||
);
|
||||
}
|
||||
|
||||
private async updateProgress(userId: number, cookies: StorygraphCookies, storygraphBookId: string, progress: number): Promise<void> {
|
||||
const { html, csrf } = await this.fetchBookPage(userId, cookies, storygraphBookId);
|
||||
const bookNumOfPages = this.extractBookNumOfPages(html);
|
||||
|
||||
Reference in New Issue
Block a user