mirror of https://github.com/Squidex/squidex.git
Browse Source
* More tests and a fix to etag handling. * Fix content form. * Tests improved. * Make username and password optional. * Simplified tests again. * Fix API testspull/1052/head
committed by
GitHub
127 changed files with 1855 additions and 667 deletions
@ -0,0 +1,18 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.AspNetCore.Builder; |
|||
|
|||
namespace Squidex.Web; |
|||
|
|||
public static class BuilderExtensions |
|||
{ |
|||
public static void UseWhenPath(this IApplicationBuilder builder, string path, Action<IApplicationBuilder> configurator) |
|||
{ |
|||
builder.UseWhen(c => c.Request.Path.StartsWithSegments(path, StringComparison.OrdinalIgnoreCase), configurator); |
|||
} |
|||
} |
|||
@ -1,58 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
namespace Squidex.Web; |
|||
|
|||
public static class ETagUtils |
|||
{ |
|||
public static string ToWeakEtag(string? etag) |
|||
{ |
|||
return $"W/{etag}"; |
|||
} |
|||
|
|||
public static bool IsStrongEtag(string etag) |
|||
{ |
|||
return !IsWeakEtag(etag.AsSpan()); |
|||
} |
|||
|
|||
public static bool IsWeakEtag(string etag) |
|||
{ |
|||
return IsWeakEtag(etag.AsSpan()); |
|||
} |
|||
|
|||
public static bool IsWeakEtag(ReadOnlySpan<char> etag) |
|||
{ |
|||
return etag.StartsWith("W/", StringComparison.OrdinalIgnoreCase); |
|||
} |
|||
|
|||
public static bool IsSameEtag(string lhs, string rhs) |
|||
{ |
|||
return IsSameEtag(lhs.AsSpan(), rhs.AsSpan()); |
|||
} |
|||
|
|||
public static bool IsSameEtag(ReadOnlySpan<char> lhs, ReadOnlySpan<char> rhs) |
|||
{ |
|||
var isMatch = lhs.Equals(rhs, StringComparison.Ordinal); |
|||
|
|||
if (isMatch) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (lhs.StartsWith("W/", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
lhs = lhs[2..]; |
|||
} |
|||
|
|||
if (rhs.StartsWith("W/", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
rhs = rhs[2..]; |
|||
} |
|||
|
|||
return lhs.Equals(rhs, StringComparison.Ordinal); |
|||
} |
|||
} |
|||
@ -0,0 +1,151 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary; |
|||
using TestSuite.Fixtures; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests; |
|||
|
|||
public class AssetScriptingTests : IClassFixture<ClientFixture> |
|||
{ |
|||
public ClientFixture _ { get; } |
|||
|
|||
public AssetScriptingTests(ClientFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_disallow_jpeg_file_on_create() |
|||
{ |
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
// STEP 0: Configure script.
|
|||
var scriptRequest = new UpdateAssetScriptsDto |
|||
{ |
|||
Create = @"
|
|||
if (ctx.command.mimeType == 'image/jpg') { |
|||
disallow('We do not use jpeg anymore.'); |
|||
}"
|
|||
}; |
|||
|
|||
await client.Apps.PutAssetScriptsAsync(scriptRequest); |
|||
|
|||
|
|||
// STEP 1: Upload jpeg.
|
|||
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg")); |
|||
|
|||
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_update_metadata_and_tags_on_create() |
|||
{ |
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
// STEP 0: Configure script.
|
|||
var scriptRequest = new UpdateAssetScriptsDto |
|||
{ |
|||
Create = @"
|
|||
ctx.command.metadata['key1'] = 'value1'; |
|||
ctx.command.metadata['key2'] = 'value2'; |
|||
ctx.command.tags.add('tag1'); |
|||
ctx.command.tags.add('tag2');"
|
|||
}; |
|||
|
|||
await client.Apps.PutAssetScriptsAsync(scriptRequest); |
|||
|
|||
|
|||
// STEP 1: Upload jpeg.
|
|||
var asset = await client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg"); |
|||
|
|||
Assert.Equal("value1", asset.Metadata["key1"]); |
|||
Assert.Equal("value2", asset.Metadata["key2"]); |
|||
Assert.Contains("tag1", asset.Tags); |
|||
Assert.Contains("tag2", asset.Tags); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_disallow_jpeg_file_on_update() |
|||
{ |
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
// STEP 0: Configure script.
|
|||
var scriptRequest = new UpdateAssetScriptsDto |
|||
{ |
|||
Update = @"
|
|||
if (ctx.command.mimeType == 'image/jpg') { |
|||
disallow('We do not use jpeg anymore.'); |
|||
}"
|
|||
}; |
|||
|
|||
await client.Apps.PutAssetScriptsAsync(scriptRequest); |
|||
|
|||
|
|||
// STEP 1. Upload initial png.
|
|||
var asset_0 = await client.Assets.UploadFileAsync("Assets/logo-wide.png", "image/png"); |
|||
|
|||
|
|||
// STEP 2: Upload jpeg.
|
|||
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpg")); |
|||
|
|||
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_update_metadata_on_update() |
|||
{ |
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
// STEP 0: Configure script.
|
|||
var scriptRequest = new UpdateAssetScriptsDto |
|||
{ |
|||
Update = @"
|
|||
ctx.command.metadata['key1'] = 'value1'; |
|||
ctx.command.metadata['key2'] = 'value2';"
|
|||
}; |
|||
|
|||
await client.Apps.PutAssetScriptsAsync(scriptRequest); |
|||
|
|||
|
|||
// STEP 1. Upload initial png.
|
|||
var asset_0 = await client.Assets.UploadFileAsync("Assets/logo-wide.png", "image/png"); |
|||
|
|||
|
|||
// STEP 2: Upload jpeg.
|
|||
var asset = await client.Assets.ReplaceFileAsync(asset_0.Id, "Assets/logo-wide-rotated.jpg", "image/jpg"); |
|||
|
|||
Assert.Equal("value1", asset.Metadata["key1"]); |
|||
Assert.Equal("value2", asset.Metadata["key2"]); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_query_asset_with_scripting() |
|||
{ |
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
// STEP 0: Configure script.
|
|||
var scriptRequest = new UpdateAssetScriptsDto |
|||
{ |
|||
Query = @"
|
|||
if (ctx.asset.mimeType == 'image/jpg') { |
|||
disallow('We do not use jpeg anymore.'); |
|||
}"
|
|||
}; |
|||
|
|||
await client.Apps.PutAssetScriptsAsync(scriptRequest); |
|||
|
|||
|
|||
// STEP 1. Upload initial jpg.
|
|||
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => client.Assets.UploadFileAsync("Assets/logo-wide-rotated.jpg", "image/jpg")); |
|||
|
|||
Assert.Contains("We do not use jpeg anymore.", ex.ToString(), StringComparison.Ordinal); |
|||
} |
|||
} |
|||
@ -0,0 +1,128 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary; |
|||
using TestSuite.Fixtures; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests; |
|||
|
|||
[UsesVerify] |
|||
public class TeamContributorTests : IClassFixture<ClientFixture> |
|||
{ |
|||
public ClientFixture _ { get; } |
|||
|
|||
public TeamContributorTests(ClientFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_invite_contributor_if_flag_is_false() |
|||
{ |
|||
// STEP 0: Create team.
|
|||
var team = await _.PostTeamAsync(); |
|||
|
|||
|
|||
// STEP 1: Do not invite contributors when flag is false.
|
|||
var createRequest = new AssignContributorDto |
|||
{ |
|||
ContributorId = "test@squidex.io" |
|||
}; |
|||
|
|||
var ex = await Assert.ThrowsAnyAsync<SquidexException>(() => |
|||
{ |
|||
return _.Client.Teams.PostContributorAsync(team.Id, createRequest); |
|||
}); |
|||
|
|||
Assert.Equal(404, ex.StatusCode); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_invite_contributor() |
|||
{ |
|||
// STEP 0: Create team.
|
|||
var team = await _.PostTeamAsync(); |
|||
|
|||
|
|||
// STEP 0: Create app.
|
|||
var (app, _) = await _.PostAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Assign contributor.
|
|||
ContributorDto contributor_1 = await InviteAsync(team.Id); |
|||
|
|||
Assert.Equal("Owner", contributor_1?.Role); |
|||
|
|||
await Verify(contributor_1) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorId) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorEmail) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_contributor() |
|||
{ |
|||
// STEP 0: Create team.
|
|||
var team = await _.PostTeamAsync(); |
|||
|
|||
|
|||
// STEP 1: Assign first contributor.
|
|||
await InviteAsync(team.Id); |
|||
|
|||
|
|||
// STEP 2: Assign other contributor.
|
|||
var contributor2 = await InviteAsync(team.Id); |
|||
|
|||
|
|||
// STEP 2: Remove contributor.
|
|||
var contributors_2 = await _.Client.Teams.DeleteContributorAsync(team.Id, contributor2.ContributorId); |
|||
|
|||
Assert.DoesNotContain(contributors_2.Items, x => x.ContributorId == contributor2.ContributorId); |
|||
|
|||
await Verify(contributors_2) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorId) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorEmail) |
|||
.IgnoreMember<ContributorDto>(x => x.ContributorName); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_not_remove_single_owner() |
|||
{ |
|||
// STEP 0: Create team.
|
|||
var team = await _.PostTeamAsync(); |
|||
|
|||
|
|||
// STEP 1: Get contributors
|
|||
var contributor = await InviteAsync(team.Id); |
|||
|
|||
|
|||
// STEP 2: Remove contributor.
|
|||
await Assert.ThrowsAnyAsync<SquidexException>(() => _.Client.Teams.DeleteContributorAsync(team.Id, contributor.ContributorId)); |
|||
} |
|||
|
|||
private async Task<ContributorDto> InviteAsync(string teamId) |
|||
{ |
|||
var email = $"{Guid.NewGuid()}@squidex.io"; |
|||
|
|||
var createInviteRequest = new AssignContributorDto |
|||
{ |
|||
ContributorId = email, |
|||
// Invite must be true, otherwise new users are not created.
|
|||
Invite = true, |
|||
// This is the only allowed role for teams.
|
|||
Role = "Owner" |
|||
}; |
|||
|
|||
var contributors = await _.Client.Teams.PostContributorAsync(teamId, createInviteRequest); |
|||
var contributor = contributors.Items.Find(x => x.ContributorName == email); |
|||
|
|||
return contributor!; |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Squidex.ClientLibrary; |
|||
using TestSuite.Fixtures; |
|||
|
|||
#pragma warning disable SA1300 // Element should begin with upper-case letter
|
|||
#pragma warning disable SA1507 // Code should not contain multiple blank lines in a row
|
|||
|
|||
namespace TestSuite.ApiTests; |
|||
|
|||
[UsesVerify] |
|||
public class TeamTests : IClassFixture<CreatedTeamFixture> |
|||
{ |
|||
public CreatedTeamFixture _ { get; } |
|||
|
|||
public TeamTests(CreatedTeamFixture fixture) |
|||
{ |
|||
_ = fixture; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_set_name() |
|||
{ |
|||
// STEP 1: Update app.
|
|||
var updateRequest = new UpdateTeamDto |
|||
{ |
|||
Name = Guid.NewGuid().ToString() |
|||
}; |
|||
|
|||
var app_1 = await _.Client.Teams.PutTeamAsync(_.TeamId, updateRequest); |
|||
|
|||
Assert.Equal(updateRequest.Name, app_1.Name); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_transfer_app_to_team() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Assign app to team.
|
|||
var transferRequest = new TransferToTeamDto |
|||
{ |
|||
TeamId = _.TeamId |
|||
}; |
|||
|
|||
var app_1 = await client.Apps.PutAppTeamAsync(transferRequest); |
|||
|
|||
Assert.Equal(_.TeamId, app_1.TeamId); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_remove_app_from_team() |
|||
{ |
|||
// STEP 0: Create app.
|
|||
var (client, _) = await _.PostAppAsync(); |
|||
|
|||
|
|||
// STEP 1: Assign app to team.
|
|||
var transferRequest = new TransferToTeamDto |
|||
{ |
|||
TeamId = _.TeamId |
|||
}; |
|||
|
|||
var app_1 = await client.Apps.PutAppTeamAsync(transferRequest); |
|||
|
|||
Assert.Equal(_.TeamId, app_1.TeamId); |
|||
|
|||
|
|||
// STEP 2: Remove app from team.
|
|||
var untransferRequest = new TransferToTeamDto |
|||
{ |
|||
TeamId = null |
|||
}; |
|||
|
|||
var app_2 = await client.Apps.PutAppTeamAsync(untransferRequest); |
|||
|
|||
Assert.Null(app_2.TeamId); |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
{ |
|||
Id: Guid_1, |
|||
ParentId: Guid_Empty, |
|||
FileName: My Image, |
|||
FileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=, |
|||
IsProtected: false, |
|||
Slug: logo-squared.png, |
|||
MimeType: image/png, |
|||
FileType: blob, |
|||
MetadataText: 600x600px, 19 kB, |
|||
Metadata: { |
|||
description: PNG File, |
|||
pixelHeight: 600, |
|||
pixelWidth: 600 |
|||
}, |
|||
Tags: [ |
|||
type/png, |
|||
image, |
|||
image/medium |
|||
], |
|||
FileSize: 19430, |
|||
Type: Image, |
|||
Version: 1, |
|||
Links: { |
|||
content: { |
|||
Method: GET |
|||
}, |
|||
content/slug: { |
|||
Method: GET |
|||
}, |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
move: { |
|||
Method: PUT |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
}, |
|||
update: { |
|||
Method: PUT |
|||
}, |
|||
upload: { |
|||
Method: PUT |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
{ |
|||
Id: Guid_1, |
|||
ParentId: Guid_Empty, |
|||
FileName: logo-squared.png, |
|||
FileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=, |
|||
IsProtected: false, |
|||
Slug: logo-squared.png, |
|||
MimeType: image/png, |
|||
FileType: png, |
|||
MetadataText: 19 kB, |
|||
Metadata: { |
|||
ph: 20, |
|||
pw: 100 |
|||
}, |
|||
Tags: [ |
|||
type/png, |
|||
image, |
|||
image/medium |
|||
], |
|||
FileSize: 19430, |
|||
Type: Image, |
|||
Version: 1, |
|||
Links: { |
|||
content: { |
|||
Method: GET |
|||
}, |
|||
content/slug: { |
|||
Method: GET |
|||
}, |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
move: { |
|||
Method: PUT |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
}, |
|||
update: { |
|||
Method: PUT |
|||
}, |
|||
upload: { |
|||
Method: PUT |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
{ |
|||
Id: Guid_1, |
|||
ParentId: Guid_Empty, |
|||
FileName: logo-squared.png, |
|||
FileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=, |
|||
IsProtected: false, |
|||
Slug: my-image, |
|||
MimeType: image/png, |
|||
FileType: png, |
|||
MetadataText: 600x600px, 19 kB, |
|||
Metadata: { |
|||
description: PNG File, |
|||
pixelHeight: 600, |
|||
pixelWidth: 600 |
|||
}, |
|||
Tags: [ |
|||
type/png, |
|||
image, |
|||
image/medium |
|||
], |
|||
FileSize: 19430, |
|||
Type: Image, |
|||
Version: 1, |
|||
Links: { |
|||
content: { |
|||
Method: GET |
|||
}, |
|||
content/slug: { |
|||
Method: GET |
|||
}, |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
move: { |
|||
Method: PUT |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
}, |
|||
update: { |
|||
Method: PUT |
|||
}, |
|||
upload: { |
|||
Method: PUT |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
{ |
|||
Id: Guid_1, |
|||
ParentId: Guid_Empty, |
|||
FileName: logo-squared.png, |
|||
FileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=, |
|||
IsProtected: false, |
|||
Slug: logo-squared.png, |
|||
MimeType: image/png, |
|||
FileType: png, |
|||
MetadataText: 600x600px, 19 kB, |
|||
Metadata: { |
|||
description: PNG File, |
|||
pixelHeight: 600, |
|||
pixelWidth: 600 |
|||
}, |
|||
Tags: [ |
|||
tag1, |
|||
tag2 |
|||
], |
|||
FileSize: 19430, |
|||
Type: Image, |
|||
Version: 1, |
|||
Links: { |
|||
content: { |
|||
Method: GET |
|||
}, |
|||
content/slug: { |
|||
Method: GET |
|||
}, |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
move: { |
|||
Method: PUT |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
}, |
|||
update: { |
|||
Method: PUT |
|||
}, |
|||
upload: { |
|||
Method: PUT |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
{ |
|||
Id: Guid_1, |
|||
ParentId: Guid_Empty, |
|||
FileName: logo-squared.png, |
|||
FileHash: pAp0RDvipkoNCCTgey1HTJekRKKEWT6Ft5JdRLuAfAc=, |
|||
IsProtected: true, |
|||
Slug: logo-squared.png, |
|||
MimeType: image/png, |
|||
FileType: png, |
|||
MetadataText: 600x600px, 19 kB, |
|||
Metadata: { |
|||
description: PNG File, |
|||
pixelHeight: 600, |
|||
pixelWidth: 600 |
|||
}, |
|||
Tags: [ |
|||
type/png, |
|||
image, |
|||
image/medium |
|||
], |
|||
FileSize: 19430, |
|||
Type: Image, |
|||
Version: 1, |
|||
Links: { |
|||
content: { |
|||
Method: GET |
|||
}, |
|||
content/slug: { |
|||
Method: GET |
|||
}, |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
move: { |
|||
Method: PUT |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
}, |
|||
update: { |
|||
Method: PUT |
|||
}, |
|||
upload: { |
|||
Method: PUT |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
{ |
|||
Role: Owner, |
|||
Links: { |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
update: { |
|||
Method: POST |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
{ |
|||
Items: [ |
|||
{ |
|||
Role: Owner, |
|||
Links: { |
|||
delete: { |
|||
Method: DELETE |
|||
}, |
|||
update: { |
|||
Method: POST |
|||
} |
|||
} |
|||
} |
|||
], |
|||
Links: { |
|||
create: { |
|||
Method: POST |
|||
}, |
|||
self: { |
|||
Method: GET |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue