Skip to content

SaveFileAsync: write hidden files again, check the SaveFilesAsync folder; test on Windows - #97

Merged
tomasr78 merged 3 commits into
mainfrom
savefile-review-fixes
Nov 12, 2026
Merged

tomasr78 merged 3 commits into
mainfrom
savefile-review-fixes

Conversation

@tomasr78

@tomasr78 tomasr78 commented Nov 11, 2026 •

Copy link
Copy Markdown
Contributor

Part of 3.3.0. This fixes what the review of the merged #84 found, before the change is released.

What changed

1. Hidden and system files on Windows (ConvertApi/ConvertApiExtension.cs)

  • FileMode.Create (CREATE_ALWAYS) throws UnauthorizedAccessException on an existing file with the Hidden or System attribute (UnauthorizedAccessException when writing to a file with Hidden attribute dotnet/runtime#117757). 3.2.2 wrote such files.
  • SaveFileAsync therefore opens the file with FileInfo.OpenWrite() again, as 3.2.2 did, and after the copy cuts it to the written length: if (fileStream.CanSeek && fileStream.Length > fileStream.Position) fileStream.SetLength(fileStream.Position);. That keeps PR 84's fix for corruption when saving over a longer file.
  • The CanSeek guard is there because an unguarded SetLength throws on a device such as /dev/null.

2. CI no longer passes with zero tests. The test step passes -- RunConfiguration.TreatNoTestsAsError=true.

3. No Windows CI. A test (windows) job was tried here and passed, then dropped by decision: the tests run on Linux only, as before. Apart from the TreatNoTestsAsError flag, build.yml and the test project are unchanged.

4. The tests no longer leak static state. SaveFileTests restores ConvertApi.ApiToken and the static client in [TearDown].

5. SaveFilesAsync checks its folder argument before downloading anything (option A).

  • It throws ArgumentException, naming SaveFileAsync, when the argument:
    • is blank;
    • is an existing file;
    • or is a missing path with no trailing separator whose extension matches a result file's, such as SaveFilesAsync("result.pdf") for PDF results.
  • Without this check, main would quietly create a folder named result.pdf, which then blocks SaveFileAsync("result.pdf").
  • "output", "", ".", "v1.2", "archive.pdf/" and an existing folder named archive.pdf all still work.
  • An empty result list returns at once and creates no folder, as in 3.2.2.

6. CHANGELOG

  • The wording is now "Every version since 2.0.0 (2018) has this bug."
  • Under Changed:
    • a folder that cannot be created now throws before the download, for example with UnauthorizedAccessException, which is not an IOException. 3.2.2 threw DirectoryNotFoundException after downloading.
    • the new ArgumentException.

The 0.0.0-local pack still shows NU5048 (PackageIconUrl). That warning was already on main and is fixed in the 3.3.0 metadata PR.

How it was tested

  • The CI steps, run locally on macOS: restore, Release build (the same 3 warnings as main), dotnet test ... -- RunConfiguration.TreatNoTestsAsError=true (17 of 17 pass), and pack.
  • New unit tests:
    • saving over a Hidden + System file. It only tests something on Windows, where FileMode.Create failed; CI runs it on Linux, where it always passes;
    • saving over a shorter file;
    • saving to /dev/null (Unix only);
    • "result.pdf", "RESULT.PDF" and "nested/result.pdf" are rejected with no folder created and no download;
    • an existing file is rejected and left unchanged;
    • a blank argument is rejected;
    • "archive.pdf/", "v1.2" and "output.d" are accepted;
    • an existing archive.pdf folder is used;
    • an empty result list creates no folder.
  • Against the staging API, a docx to pdf conversion with this branch:
    • SaveFileAsync over a 400,000-byte file left exactly the 146,734-byte PDF;
    • SaveFilesAsync into a missing nested folder created it and saved the file;
    • SaveFilesAsync(".../result.pdf") threw ArgumentException and created no folder;
    • DeleteFilesAsync removed both server files.
  • Read the API token from CONVERTAPI_TOKEN when none is set in code #96 is merged into main, and main is merged into this branch (CHANGELOG auto-merged): 23 of 23 tests pass.

🤖 Generated with Claude Code

…der; test on Windows

Fixes what the review of PR 84 found:
- SaveFileAsync opens with FileInfo.OpenWrite again and cuts the file to the
  written length after the copy. FileMode.Create fails on an existing hidden
  or system file on Windows, which 3.2.2 wrote. The trim is skipped for a
  stream that cannot seek, such as /dev/null.
- SaveFilesAsync throws ArgumentException before downloading when the folder
  is blank, an existing file, or looks like a result file name
  (SaveFilesAsync("result.pdf")), instead of creating a folder named
  result.pdf. An empty result list creates no folder, as in 3.2.2.
- The unit tests restore the static ApiToken and client they replace.
- build.yml runs the unit tests on Windows too, on net10.0 and net48, and
  fails when no test runs. The build job waits for the Windows job and fails
  if it failed, so build stays the one required check.
- CHANGELOG: the bug dates from 2.0.0 (2018); the exception changes are
  recorded under Changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 11, 2026 13:14

Copilot AI 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.

🟢 Approval recommended

No active review issues block the file-saving and test-coverage changes.

No open issues 🎉

What changed in this PR

This PR refines the planned 3.3.0 file-saving fixes, restoring support for hidden and system files on Windows while preserving correct truncation when overwriting longer files.

Changes:

  • Validate SaveFilesAsync destinations before downloading, while preserving supported folder paths and empty-result behavior.
  • Expand file-saving regression tests and restore shared client and token state after each test.
  • Add Windows testing for .NET 10 and .NET Framework 4.8, and configure CI to fail when no tests run.
File Description
ConvertApi/​ConvertApiExtension.cs Restore OpenWrite with post-copy truncation and validate output folders.
ConvertApi.Tests/​SaveFileTests.cs Add overwrite and folder-validation tests; restore static state after tests.
ConvertApi.Tests/​ConvertApi.Tests.csproj Add Windows-only .NET Framework 4.8 targeting and required imports.
CHANGELOG.md Document file-saving behavior, compatibility changes, and expanded CI coverage.
.github/​workflows/​build.yml Add Windows tests, gate the required build on their success, and reject zero-test runs.

Review effort: Balanced

tomasr78 and others added 2 commits November 12, 2026 09:33
The tests run on Linux only again, in the one build job, which keeps
TreatNoTestsAsError. build.yml and the test project match main apart
from that flag.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 12, 2026 07:34

Copilot AI 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.

🟢 Approval recommended

No active review issues are recorded for the file-saving changes.

No open issues 🎉

Review effort: Balanced

@tomasr78
tomasr78 merged commit b05a04d into main Nov 12, 2026
4 checks passed
@tomasr78
tomasr78 deleted the savefile-review-fixes branch November 12, 2026 07:40
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.

2 participants