Skip to content

Fix #4651 default asset languagecode#4662

Open
kevmtt wants to merge 13 commits into
vendurehq:masterfrom
kevmtt:fix-#4651-default-asset-languagecode
Open

Fix #4651 default asset languagecode#4662
kevmtt wants to merge 13 commits into
vendurehq:masterfrom
kevmtt:fix-#4651-default-asset-languagecode

Conversation

@kevmtt
Copy link
Copy Markdown
Contributor

@kevmtt kevmtt commented Apr 20, 2026

Description

Fixes #4651.

Since v3.6, AssetService.createFromFileStream() fails with a NOT NULL constraint violation on asset_translation.languageCode when called without an explicit RequestContext. The root cause: the fallback used RequestContext.empty(), which constructs new Channel() with no defaultLanguageCode, leaving ctx.languageCode as undefined when createAssetInternal builds the default translation.

This PR introduces a new RequestContextService.createDefaultContext() method that builds a RequestContext based on the default Channel, guaranteeing a valid languageCode (falling back to ConfigService.defaultLanguageCode if the channel has none) and currencyCode. AssetService.createFromFileStream() now uses this helper instead of RequestContext.empty()` when no ctx is supplied, restoring the pre-v3.6 behavior.

Changes:

  • packages/core/src/service/helpers/request-context/request-context.service.ts — add createDefaultContext() (admin API type, authorized, derived from the default channel).
  • packages/core/src/service/services/asset.service.ts — inject RequestContextService and use createDefaultContext() as the fallback in createFromFileStream.
  • packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts — new e2e test reproducing the original issue by calling
    createFromFileStream without a RequestContext.
  • docs/.../request-context-service.mdx — documents the new method.

Breaking changes

None. createDefaultContext() is additive, and the asset service change only affects the previously-broken path where no
RequestContext is passed (which threw a DB constraint error before).

Screenshots

N/A — backend-only change.

Checklist

📌 Always:

  • I have set a clear title
  • My PR is small and contains a single feature
  • I have checked my own PR

👍 Most of the time:

  • I have added or updated test cases
  • I have updated the README if needed

@vendure-developer-hub
Copy link
Copy Markdown

vendure-developer-hub Bot commented Apr 20, 2026

Vendure CoreView preview

8c4b6df

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vendure-storybook Ready Ready Preview, Comment Jun 2, 2026 9:17am

Request Review

@vendure-ci-automation-bot
Copy link
Copy Markdown
Contributor

vendure-ci-automation-bot Bot commented Apr 20, 2026


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


2 out of 3 committers have signed the CLA.
✅ (kevmtt)[https://github.com/kevmtt]
✅ (michaelbromley)[https://github.com/michaelbromley]
@kevin
Kevin seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 20da9b30-dd1e-47b1-808c-b08218d51e8b

📥 Commits

Reviewing files that changed from the base of the PR and between 3c1e85c and 46f835e.

📒 Files selected for processing (1)
  • packages/core/src/service/services/asset.service.ts

📝 Walkthrough

Walkthrough

Added RequestContextService.createDefaultContext(config?) which builds a RequestContext from the default Channel (deriving languageCode and currencyCode) and fixed defaults for apiType/isAuthorized/authorizedAsOwnerOnly. Refactored RequestContextService.create to accept an exported CreateRequestContextConfig interface and added a CreateDefaultRequestContextConfig type alias. AssetService now injects RequestContextService and calls createDefaultContext() when no RequestContext is provided. Extended allowed asset file types to include .md and added an e2e test for creating an asset from an in-memory .md stream.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix #4651 default asset languagecode' clearly and concisely summarizes the main change: resolving the issue where AssetService.createFromFileStream fails due to missing languageCode when no RequestContext is provided.
Description check ✅ Passed The description comprehensively covers all required sections: it explains the problem, root cause, solution, lists all changes, explicitly states no breaking changes, includes test coverage, and marks checklist items appropriately.
Linked Issues check ✅ Passed The PR fully addresses issue #4651 by introducing RequestContextService.createDefaultContext() to ensure valid languageCode from the default channel, updating AssetService.createFromFileStream to use this fallback, and adding an e2e test validating the fix.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing issue #4651: new RequestContextService method, AssetService integration, e2e test, and documentation updates are all necessary and directly related to the languageCode issue.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@kevmtt
Copy link
Copy Markdown
Contributor Author

kevmtt commented Apr 20, 2026

I created the e2e test first, confirmed it failed and then added the fix.
image

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
packages/core/src/service/helpers/request-context/request-context.service.ts (1)

41-51: LGTM — sensible fallback chain for languageCode.

The channel.defaultLanguageCode ?? this.configService.defaultLanguageCode fallback is a nice defensive touch: although Channel.defaultLanguageCode is a non-nullable column, the extra guard handles the edge case where the default channel is somehow missing the value, matching the intent of restoring pre-v3.6 behaviour.

One minor thought: isAuthorized: true / authorizedAsOwnerOnly: false mirrors RequestContext.empty(), so callers effectively get full admin authority. That's consistent with existing behaviour, but worth being explicit about in the JSDoc so users know this context bypasses permission checks — same caveat that applies to RequestContext.empty().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/service/helpers/request-context/request-context.service.ts`
around lines 41 - 51, Add a JSDoc comment to createDefaultContext explaining
that it sets isAuthorized: true and authorizedAsOwnerOnly: false (matching
RequestContext.empty()) and therefore grants full admin authority and bypasses
permission checks; mention that callers should treat this context as an
elevated/admin context and that the languageCode fallback uses
channel.defaultLanguageCode ?? this.configService.defaultLanguageCode to
preserve pre-v3.6 behavior.
packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts (2)

19-25: Consider skipping the products CSV import for this focused test.

server.init with productsCsvPath and customerCount: 1 pulls in unrelated fixtures just to exercise createFromFileStream. Since the test only needs a running server + default channel, you can speed this suite up and reduce coupling to the fixtures directory by dropping the CSV / customer seed:

Proposed simplification
-    beforeAll(async () => {
-        await server.init({
-            initialData,
-            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
-            customerCount: 1,
-        });
-    }, TEST_SETUP_TIMEOUT_MS);
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            customerCount: 0,
+        });
+    }, TEST_SETUP_TIMEOUT_MS);

If you drop the CSV, the path import can also go.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts` around lines 19
- 25, The test seeds unrelated fixtures by passing productsCsvPath and
customerCount to server.init; remove those options from the server.init call in
asset-create-from-file-stream.e2e-spec.ts so the test only starts the server
with initialData (and the default channel), and also remove the unused path
import at the top of the file. Update the beforeAll to call server.init({
initialData }) (leave TEST_SETUP_TIMEOUT_MS unchanged) and delete the
productsCsvPath and customerCount references so the suite runs faster and is
decoupled from fixtures.

39-45: Tighten the assertion — the 'id' in result guard can hide a regression.

If createFromFileStream ever returns a MimeTypeError (which also has no id), the current assertions would fail via expect('id' in result).toBe(true) — good — but the result.name check is then silently skipped by the if ('id' in result) guard, so any future regression in the default translation name wouldn't fail the test.

Consider asserting the error-free path more directly so the name check always executes:

Proposed tweak
-        const result = await assetService.createFromFileStream(stream, 'test-file.txt');
-
-        expect(result).toBeDefined();
-        expect('id' in result).toBe(true);
-        if ('id' in result) {
-            expect(result.name).toBe('test-file.txt');
-        }
+        const result = await assetService.createFromFileStream(stream, 'test-file.txt');
+
+        expect(result).toBeDefined();
+        if (!('id' in result)) {
+            throw new Error(`Expected Asset, got error result: ${JSON.stringify(result)}`);
+        }
+        expect(result.name).toBe('test-file.txt');
+        expect(result.languageCode).toBeDefined();

The extra languageCode assertion would also explicitly cover the issue #4651 regression you're guarding against.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts` around lines 39
- 45, The test currently uses an `if ('id' in result)` guard which can silently
skip the name assertion if `result` is an error-like object; change the
assertions to explicitly require the success shape so failures aren’t masked:
assert that `result.id` is defined (e.g., expect(result.id).toBeDefined()), then
unconditionally assert `result.name` equals 'test-file.txt' and add an explicit
assertion for `result.languageCode` (e.g.,
expect(result.languageCode).toBe('en')) after calling
assetService.createFromFileStream so the success path is enforced and the `#4651`
regression is covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts`:
- Around line 19-25: The test seeds unrelated fixtures by passing
productsCsvPath and customerCount to server.init; remove those options from the
server.init call in asset-create-from-file-stream.e2e-spec.ts so the test only
starts the server with initialData (and the default channel), and also remove
the unused path import at the top of the file. Update the beforeAll to call
server.init({ initialData }) (leave TEST_SETUP_TIMEOUT_MS unchanged) and delete
the productsCsvPath and customerCount references so the suite runs faster and is
decoupled from fixtures.
- Around line 39-45: The test currently uses an `if ('id' in result)` guard
which can silently skip the name assertion if `result` is an error-like object;
change the assertions to explicitly require the success shape so failures aren’t
masked: assert that `result.id` is defined (e.g.,
expect(result.id).toBeDefined()), then unconditionally assert `result.name`
equals 'test-file.txt' and add an explicit assertion for `result.languageCode`
(e.g., expect(result.languageCode).toBe('en')) after calling
assetService.createFromFileStream so the success path is enforced and the `#4651`
regression is covered.

In
`@packages/core/src/service/helpers/request-context/request-context.service.ts`:
- Around line 41-51: Add a JSDoc comment to createDefaultContext explaining that
it sets isAuthorized: true and authorizedAsOwnerOnly: false (matching
RequestContext.empty()) and therefore grants full admin authority and bypasses
permission checks; mention that callers should treat this context as an
elevated/admin context and that the languageCode fallback uses
channel.defaultLanguageCode ?? this.configService.defaultLanguageCode to
preserve pre-v3.6 behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d1cacf2a-a6cb-4618-85d6-fa6b5118369e

📥 Commits

Reviewing files that changed from the base of the PR and between a3f5a92 and 5ad0d25.

📒 Files selected for processing (4)
  • docs/docs/reference/typescript-api/request/request-context-service.mdx
  • packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts
  • packages/core/src/service/helpers/request-context/request-context.service.ts
  • packages/core/src/service/services/asset.service.ts

@kevmtt
Copy link
Copy Markdown
Contributor Author

kevmtt commented Apr 20, 2026

I have read the CLA Document and I hereby sign the CLA

Comment thread packages/core/e2e/asset-create-from-file-stream.e2e-spec.ts Outdated
Comment thread packages/core/src/service/helpers/request-context/request-context.service.ts Outdated
Comment thread packages/core/src/service/helpers/request-context/request-context.service.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/e2e/asset.e2e-spec.ts`:
- Around line 363-378: The test "create an asset from a file stream without
RequestContext" is using an unsupported extension ('test-file.txt') and either
should assert a MIME-type error or use a permitted extension and clean up the
created asset; update the call to assetService.createFromFileStream to use a
permitted filename (e.g. 'test-file.png') and after a successful create call
assetService.delete(result.id) (or the appropriate deletion method on
AssetService) to remove the created asset so the shared assets.totalItems
assertion is not affected; alternatively change the expectation to assert that
createFromFileStream throws a MIME type error when given 'test-file.txt'
(referencing AssetService.createFromFileStream and the suite's assets.totalItems
assertion).

In
`@packages/core/src/service/helpers/request-context/request-context.service.ts`:
- Around line 42-44: The createDefaultContext method's config parameter is
currently required but should be optional per the JSDoc; make it optional by
giving the parameter a default of an empty object so callers can invoke
createDefaultContext() with no args. Update the signature of
createDefaultContext(config: Partial<ConstructorParameters<typeof
RequestContext>[0]> = {}) to provide the {} default, ensuring RequestContext
construction inside the method still works with partial/empty config (adjust any
destructuring or usage inside createDefaultContext to handle missing properties
if necessary).
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7b68d273-45ab-4203-8037-425bd16a62f2

📥 Commits

Reviewing files that changed from the base of the PR and between 5ad0d25 and 2afbd36.

📒 Files selected for processing (3)
  • docs/docs/reference/typescript-api/request/request-context-service.mdx
  • packages/core/e2e/asset.e2e-spec.ts
  • packages/core/src/service/helpers/request-context/request-context.service.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/docs/reference/typescript-api/request/request-context-service.mdx

Comment thread packages/core/e2e/asset.e2e-spec.ts
Comment thread packages/core/src/service/helpers/request-context/request-context.service.ts Outdated
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.

AssetService.createFromFileStream() fails without RequestContext because of missing languageCode

2 participants