Browse Source

Cover Angular signature/method mappers and pin jQuery multi-FormFile limit

pull/25639/head
maliming 3 months ago
parent
commit
1a65d79c6a
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 15
      framework/test/Volo.Abp.Http.Tests/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator_ContentTypes_Tests.cs
  2. 128
      npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts

15
framework/test/Volo.Abp.Http.Tests/Volo/Abp/Http/ProxyScripting/Generators/JQuery/JQueryProxyScriptGenerator_ContentTypes_Tests.cs

@ -167,6 +167,21 @@ public class JQueryProxyScriptGenerator_ContentTypes_Tests
script.ShouldNotContain("JSON.stringify");
}
[Fact]
public void Multiple_Direct_FormFile_Params_Should_Forward_Only_First_Var_Known_Limitation()
{
var script = _generator.CreateScript(BuildUploadModel(
uploadParameters: new[]
{
("file1", "file1", ParameterBindingSources.FormFile),
("file2", "file2", ParameterBindingSources.FormFile),
}));
script.ShouldContain("data: file1");
script.ShouldNotContain("data: file2");
script.ShouldNotContain("$.merge(file1, file2)");
}
private static ApplicationApiDescriptionModel BuildAppModel(string returnType, IList<string>? contentTypes, bool isRemoteStream = false)
{
var model = ApplicationApiDescriptionModel.Create();

128
npm/ng-packs/packages/schematics/src/tests/action-to-body-mapper.spec.ts

@ -3,7 +3,11 @@ import { join } from 'path';
import { describe, expect, test } from 'vitest';
import { eBindingSourceId } from '../enums';
import { Action } from '../models';
import { createActionToBodyMapper } from '../utils/service';
import {
createActionToBodyMapper,
createActionToMethodMapper,
createActionToSignatureMapper,
} from '../utils/service';
const TEMPLATE_PATH = join(
__dirname,
@ -704,3 +708,125 @@ describe('createActionToBodyMapper — multipart upload params regression', () =
expect(body.params.join(',')).not.toContain('name');
});
});
describe('createActionToSignatureMapper — multipart upload signature collapse', () => {
const mapSignature = createActionToSignatureMapper();
test('FormFile DTO method arg collapses to FormData type', () => {
const sig = mapSignature(buildAction({
httpMethod: 'POST',
url: 'api/app/proxy-demo-test/upload-single',
parametersOnMethod: [
{ name: 'input', type: 'AbpProxyDemo.UploadDto', typeAsString: 'AbpProxyDemo.UploadDto', typeSimple: 'AbpProxyDemo.UploadDto', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'input', name: 'Name', type: 'System.String', typeSimple: 'string', bindingSourceId: eBindingSourceId.ModelBinding } as any,
{ nameOnMethod: 'input', name: 'File', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
],
} as Partial<Action>));
const types = sig.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(types).toContain('input:FormData');
expect(types[types.length - 1]).toBe('config:Partial<Rest.Config>');
});
test('Path arg keeps its primitive type while upload arg becomes FormData', () => {
const sig = mapSignature(buildAction({
httpMethod: 'POST',
url: 'api/proxy-demo/media/upload-with-path/{id}',
parametersOnMethod: [
{ name: 'id', type: 'System.Int32', typeAsString: 'System.Int32', typeSimple: 'number', isOptional: false, defaultValue: null } as any,
{ name: 'input', type: 'AbpProxyDemo.UploadDto', typeAsString: 'AbpProxyDemo.UploadDto', typeSimple: 'AbpProxyDemo.UploadDto', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'id', name: 'id', type: 'System.Int32', typeSimple: 'int', bindingSourceId: eBindingSourceId.Path } as any,
{ nameOnMethod: 'input', name: 'Name', type: 'System.String', typeSimple: 'string', bindingSourceId: eBindingSourceId.ModelBinding } as any,
{ nameOnMethod: 'input', name: 'File', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
],
} as Partial<Action>));
const types = sig.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(types).toEqual([
'id:number',
'input:FormData',
'config:Partial<Rest.Config>',
]);
});
test('Multiple direct IRemoteStreamContent method args each become FormData independently', () => {
const sig = mapSignature(buildAction({
httpMethod: 'POST',
url: 'api/proxy-demo/media/upload-two-direct',
parametersOnMethod: [
{ name: 'file1', type: 'Volo.Abp.Content.IRemoteStreamContent', typeAsString: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', isOptional: false, defaultValue: null } as any,
{ name: 'file2', type: 'Volo.Abp.Content.IRemoteStreamContent', typeAsString: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'file1', name: 'file1', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
{ nameOnMethod: 'file2', name: 'file2', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
],
} as Partial<Action>));
const types = sig.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(types).toEqual([
'file1:FormData',
'file2:FormData',
'config:Partial<Rest.Config>',
]);
});
test('Non-upload action signature stays untouched (regression guard)', () => {
const sig = mapSignature(buildAction({
httpMethod: 'GET',
url: 'api/app/proxy-demo-test/get-item-by-id',
parametersOnMethod: [
{ name: 'id', type: 'System.Int32', typeAsString: 'System.Int32', typeSimple: 'number', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'id', name: 'id', type: 'System.Int32', typeSimple: 'int', bindingSourceId: eBindingSourceId.Path } as any,
],
} as Partial<Action>));
const types = sig.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(types).toEqual(['id:number', 'config:Partial<Rest.Config>']);
});
});
describe('createActionToMethodMapper — signature + body wired together for upload actions', () => {
const mapMethod = createActionToMethodMapper();
test('Upload action produces both FormData signature and FormData body in one pass', () => {
const method = mapMethod(buildAction({
httpMethod: 'POST',
url: 'api/app/proxy-demo-test/upload-single',
parametersOnMethod: [
{ name: 'input', type: 'AbpProxyDemo.UploadDto', typeAsString: 'AbpProxyDemo.UploadDto', typeSimple: 'AbpProxyDemo.UploadDto', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'input', name: 'Name', type: 'System.String', typeSimple: 'string', bindingSourceId: eBindingSourceId.ModelBinding } as any,
{ nameOnMethod: 'input', name: 'File', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
],
} as Partial<Action>));
const sigTypes = method.signature.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(sigTypes).toContain('input:FormData');
expect(method.body.body).toBe('input');
expect(method.body.params.join(',')).not.toContain('name');
});
test('Query + ModelBinding + FormFile mix preserves Query as URL param and drops upload-arg fields', () => {
const method = mapMethod(buildAction({
httpMethod: 'POST',
url: 'api/proxy-demo/media/upload-with-query',
parametersOnMethod: [
{ name: 'tag', type: 'System.String', typeAsString: 'System.String', typeSimple: 'string', isOptional: false, defaultValue: null } as any,
{ name: 'input', type: 'AbpProxyDemo.UploadDto', typeAsString: 'AbpProxyDemo.UploadDto', typeSimple: 'AbpProxyDemo.UploadDto', isOptional: false, defaultValue: null } as any,
],
parameters: [
{ nameOnMethod: 'tag', name: 'tag', type: 'System.String', typeSimple: 'string', bindingSourceId: eBindingSourceId.Query } as any,
{ nameOnMethod: 'input', name: 'Name', type: 'System.String', typeSimple: 'string', bindingSourceId: eBindingSourceId.ModelBinding } as any,
{ nameOnMethod: 'input', name: 'File', type: 'Volo.Abp.Content.IRemoteStreamContent', typeSimple: 'Volo.Abp.Content.IRemoteStreamContent', bindingSourceId: eBindingSourceId.FormFile } as any,
],
} as Partial<Action>));
const sigTypes = method.signature.parameters.map((p: any) => `${p.name}:${p.type}`);
expect(sigTypes).toEqual(['tag:string', 'input:FormData', 'config:Partial<Rest.Config>']);
expect(method.body.body).toBe('input');
expect(method.body.params).toContain('tag');
expect(method.body.params.join(',')).not.toContain('name');
});
});

Loading…
Cancel
Save