Skip to content

fix: Ghostfolio v3 compat, date preservation, rmSync safety - #334

Open
dominatos wants to merge 8 commits into
dickwolff:mainfrom
dominatos:fix/ghostfolio-v3-compat
Open

fix: Ghostfolio v3 compat, date preservation, rmSync safety#334
dominatos wants to merge 8 commits into
dickwolff:mainfrom
dominatos:fix/ghostfolio-v3-compat

Conversation

@dominatos

@dominatos dominatos commented Jul 21, 2026

Copy link
Copy Markdown

Fixes three bugs that break the converter with Ghostfolio v3.30.0+ and Trading212 CSV exports.

Problem

  1. Auth endpoint changed — Ghostfolio v3.30.0 moved anonymous auth from GET /api/v1/auth/anonymous/{secret} to POST /api/v1/auth/anonymous with { accessToken } body. The old GET format returns 404, which triggers infinite retry loops that exhaust the 4GB heap (OOM crash).
  2. All activity dates wrongprocessHeaders() camelizes Time (UTC) to timeUTC, but the converter accesses record.time. Since record.time is undefined, dayjs(undefined) falls back to today's date for every activity. This means buy/sell/dividend dates are all lost.
  3. rmSync crash on cleanupfs.rmSync(filePath) throws ENOENT when the orchestrator script has already removed the input file before the container finishes processing.
  4. Infinite retry on auth failureretryCount++ (postfix) passes the original value to recursive calls, so the retryCount === 3 termination is never reached.
  5. Silent auth failuresauthenticate() doesn't check response.ok before parsing JSON, so a failed auth (wrong secret, network error) silently sets cachedBearerToken to undefined.

Changes

File Fix
src/ghostfolioService.ts POST auth with JSON body + response.ok check + fix retryCount to retryCount + 1
src/converters/abstractconverter.ts Map timeUTC header to time in processHeaders()
src/watcher.ts { force: true } on both rmSync calls

Testing

Verified against a Trading212 CSV with 970 rows (buys, sells, dividends, interest):

  • All dates preserved correctly (previously all showed today's date)
  • Validation passes, import succeeds (950 activities imported)
  • No OOM, no rmSync crashes, no infinite retries

Added

Fixes

Checklist

  • Added relevant changes to README (if applicable)
  • Added relevant test(s)
  • Updated the GitVersion file (if not done automatically)

Related issue (if applicable)

Fixes #..

- Auth: use POST /api/v1/auth/anonymous with accessToken body (Ghostfolio v3+)
- Dates: map timeUTC header to time so timestamps are preserved
- rmSync: use { force: true } to avoid ENOENT when input already cleaned
@dominatos
dominatos requested a review from dickwolff as a code owner July 21, 2026 15:29
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The converter normalizes timeUTC headers to time and adds mapping tests. Ghostfolio authentication now uses a POST JSON payload, validates responses, and increments unauthorized retry counts correctly, with corresponding tests. Watcher cleanup force-removes input files, and the next version is updated to 0.34.2.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fixes: Ghostfolio v3 compatibility, date preservation, and rmSync cleanup safety.
Description check ✅ Passed The description matches the template and covers the bugs, changes, testing, checklist, and issue reference, with only minor placeholders left.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ghostfolioService.ts (1)

55-55: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Infinite loop due to postfix increment operator.

The postfix increment (retryCount++) evaluates to the current value of retryCount before incrementing it. As a result, the recursive calls always receive the original value (e.g., 0), meaning the termination condition retryCount === 3 is never met. This results in an infinite recursion loop if authentication continuously fails.

Replace retryCount++ with retryCount + 1 to correctly increment the counter for the next recursive call.

  • src/ghostfolioService.ts#L55-L55: Replace retryCount++ with retryCount + 1 in the validate method.
  • src/ghostfolioService.ts#L110-L110: Replace retryCount++ with retryCount + 1 in the import method.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ghostfolioService.ts` at line 55, Replace the postfix retryCount++
argument with retryCount + 1 in both the validate method at
src/ghostfolioService.ts:55-55 and the import method at
src/ghostfolioService.ts:110-110, ensuring recursive calls advance the retry
counter and reach the termination condition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ghostfolioService.ts`:
- Around line 137-144: Update the authentication flow around bearerResponse and
cachedBearerToken to verify bearerResponse.ok before parsing its JSON. Handle
unsuccessful responses by raising or propagating an authentication error, and
only assign bearer.authToken after confirming the response succeeded and
contains a valid token.

---

Outside diff comments:
In `@src/ghostfolioService.ts`:
- Line 55: Replace the postfix retryCount++ argument with retryCount + 1 in both
the validate method at src/ghostfolioService.ts:55-55 and the import method at
src/ghostfolioService.ts:110-110, ensuring recursive calls advance the retry
counter and reach the termination condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa754b7e-af6c-4526-b851-cb57d429578f

📥 Commits

Reviewing files that changed from the base of the PR and between 9e35bd6 and 8241977.

📒 Files selected for processing (3)
  • src/converters/abstractconverter.ts
  • src/ghostfolioService.ts
  • src/watcher.ts

Comment thread src/ghostfolioService.ts Outdated
@dominatos

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

- ghostfolioService: POST auth, response.ok check, retryCount termination
- abstractconverter: timeUTC → time header mapping for Trading212
- Bump GitVersion to 0.34.2

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
src/abstractconverter.test.ts (1)

5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Expose the protected method via the test subclass to avoid any casting.

Using (converter as any).processHeaders(...) circumvents TypeScript's type checking. Since TestConverter is already a dedicated test subclass, you can safely expose the protected method through a public wrapper. This maintains type safety if the signature of processHeaders ever changes.

You can then update the test cases to use converter.exposeProcessHeaders(csvContent).

♻️ Proposed refactor
 class TestConverter extends AbstractConverter {
     constructor(securityService: SecurityService) {
         super(securityService);
     }
     isIgnoredRecord(record: any): boolean {
         return false;
     }
     processFileContents(input: string, successCallback: CallableFunction, errorCallback: CallableFunction): void {
         // not needed for header tests
     }
+
+    public exposeProcessHeaders(csvFile: string, splitChar = ","): string[] {
+        return this.processHeaders(csvFile, splitChar);
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/abstractconverter.test.ts` around lines 5 - 16, Expose
AbstractConverter’s protected processHeaders method through a public
exposeProcessHeaders wrapper on TestConverter, preserving its typed signature
and forwarding arguments. Update the header tests to call
converter.exposeProcessHeaders(csvContent) instead of casting converter to any
and invoking processHeaders directly.
src/ghostfolioService.test.ts (2)

113-137: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid filesystem side-effects and cross-platform issues by mocking fs.

Hardcoding /tmp/ can cause tests to fail on Windows environments where this directory might not exist. Additionally, if the expect assertion fails, the test will abort before reaching fs.unlinkSync, leaving a lingering file on the disk.

Consider mocking fs.readFileSync to avoid filesystem side-effects entirely, which removes the need for file creation and deletion.

♻️ Proposed refactor
-            const tmpFile = "/tmp/test-validate.json";
-            fs.writeFileSync(tmpFile, JSON.stringify({ activities: [] }));
+            const tmpFile = "dummy-test-validate.json";
+            const fsSpy = jest.spyOn(fs, "readFileSync").mockReturnValue(JSON.stringify({ activities: [] }));
 
             // Mock fetch: auth always succeeds, validate always returns 401
             mockFetch.mockImplementation(async (url: string) => {
                 if (url.includes("/auth/anonymous")) {
                     return { ok: true, json: async () => ({ authToken: "token" }) };
                 }
                 // validate endpoint - always 401 to trigger retry
                 return { ok: true, status: 401, json: async () => ({}) };
             });
 
             // Act & Assert - should throw after 3 retries (retryCount 0→1→2→3)
             await expect(service.validate(tmpFile, 0))
                 .rejects.toThrow("Failed to validate export file because of authentication error");
 
             // Auth should have been called 3 times (retryCount 0→1→2, then 3 throws before auth)
             const authCalls = mockFetch.mock.calls.filter(
                 (call: any) => call[0].includes("/auth/anonymous")
             );
             expect(authCalls.length).toBe(3);
 
             // Cleanup
-            fs.unlinkSync(tmpFile);
+            fsSpy.mockRestore();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ghostfolioService.test.ts` around lines 113 - 137, Update the validate
retry test to mock fs.readFileSync with the expected export contents instead of
creating a hardcoded /tmp/test-validate.json file. Remove the writeFileSync and
unlinkSync setup/cleanup while preserving the existing authentication retry
assertions and validate expectation.

32-43: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove leftover mock setup and comments.

The second mockFetch.mockResolvedValueOnce and the comments above it are remnants of an older test strategy that tried to trigger authenticate via validate. Since authenticate(true) is now called directly and only makes a single fetch request, this setup is unused and can be confusing.

♻️ Proposed refactor
-            // Act - trigger authenticate via validate (which calls authenticate on 401)
-            // First call: validate gets 401, triggers authenticate
-            mockFetch.mockResolvedValueOnce({
-                ok: true,
-                status: 201,
-                json: async () => ({})
-            });
-
-            // We need to call validate to trigger authenticate
-            // But validate reads a file, so let's test authenticate indirectly
-            // by checking the fetch call format
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ghostfolioService.test.ts` around lines 32 - 43, Remove the unused second
mockFetch.mockResolvedValueOnce setup and the outdated comments describing
indirect authenticate triggering in the affected test. Keep the direct
authenticate(true) invocation and its single fetch mock unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/abstractconverter.test.ts`:
- Around line 5-16: Expose AbstractConverter’s protected processHeaders method
through a public exposeProcessHeaders wrapper on TestConverter, preserving its
typed signature and forwarding arguments. Update the header tests to call
converter.exposeProcessHeaders(csvContent) instead of casting converter to any
and invoking processHeaders directly.

In `@src/ghostfolioService.test.ts`:
- Around line 113-137: Update the validate retry test to mock fs.readFileSync
with the expected export contents instead of creating a hardcoded
/tmp/test-validate.json file. Remove the writeFileSync and unlinkSync
setup/cleanup while preserving the existing authentication retry assertions and
validate expectation.
- Around line 32-43: Remove the unused second mockFetch.mockResolvedValueOnce
setup and the outdated comments describing indirect authenticate triggering in
the affected test. Keep the direct authenticate(true) invocation and its single
fetch mock unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 819f4296-efd2-429b-8e5f-91a027a8ac97

📥 Commits

Reviewing files that changed from the base of the PR and between e1da706 and 79fb647.

📒 Files selected for processing (3)
  • GitVersion.yml
  • src/abstractconverter.test.ts
  • src/ghostfolioService.test.ts

…ock fs instead of temp file, remove unused mocks
@dominatos

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant