forked from xiongyuxing/tiku-backend.net
575 lines
24 KiB
C#
575 lines
24 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Tiku.Api.Contracts;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Application.Storage;
|
|
using Tiku.Domain.Common;
|
|
using Tiku.Domain.Content;
|
|
using Tiku.Domain.Identity;
|
|
using Tiku.Domain.Platform;
|
|
using Tiku.Domain.Tenancy;
|
|
using Tiku.Infrastructure.Auth;
|
|
using Tiku.Infrastructure.Persistence;
|
|
|
|
namespace Tiku.IntegrationTests.Api;
|
|
|
|
public sealed class AssetManagementEndpointTests
|
|
{
|
|
[Fact]
|
|
public async Task Tenant_content_upload_sign_requires_authentication()
|
|
{
|
|
await using var factory = new ApiTestFactory(objectStorageService: new FakeObjectStorageService());
|
|
using var client = factory.CreateClient();
|
|
|
|
using var response = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/sign",
|
|
new AssetUploadSignDto
|
|
{
|
|
FileName = "lesson.pdf",
|
|
MimeType = "application/pdf"
|
|
});
|
|
|
|
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Tenant_admin_can_sign_upload_and_create_pending_asset()
|
|
{
|
|
await using var factory = new ApiTestFactory(objectStorageService: new FakeObjectStorageService());
|
|
var seed = await SeedAdminAsync(factory);
|
|
using var client = factory.CreateClient();
|
|
await LoginAsync(client, seed);
|
|
|
|
using var response = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/sign",
|
|
new AssetUploadSignDto
|
|
{
|
|
FileName = "lesson.pdf",
|
|
MimeType = "application/pdf",
|
|
FileSizeBytes = 1024,
|
|
Title = "课程讲义",
|
|
AssetType = "pdf",
|
|
Visibility = "members"
|
|
});
|
|
var body = await ReadJsonAsync(response);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal("PUT", body.RootElement.GetProperty("upload").GetProperty("method").GetString());
|
|
var item = body.RootElement.GetProperty("item");
|
|
var assetId = item.GetProperty("id").GetGuid();
|
|
Assert.Equal("Pending", item.GetProperty("uploadStatus").GetString());
|
|
Assert.Equal("AliyunOss", item.GetProperty("storageProvider").GetString());
|
|
Assert.StartsWith($"{seed.TenantId:N}/assets/", item.GetProperty("objectKey").GetString(), StringComparison.Ordinal);
|
|
|
|
using var listResponse = await client.GetAsync("/api/tenant-content/assets?uploadStatus=pending");
|
|
var list = await ReadJsonAsync(listResponse);
|
|
var listItem = Assert.Single(list.RootElement.GetProperty("items").EnumerateArray());
|
|
Assert.Equal(assetId, listItem.GetProperty("id").GetGuid());
|
|
|
|
using var scope = factory.CreateSystemScope();
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
|
var asset = dbContext.ContentAssets.Single(asset => asset.Id == assetId);
|
|
Assert.Equal(seed.TenantId, asset.TenantId);
|
|
Assert.Equal(seed.UserId, asset.CreatedBy);
|
|
Assert.Equal(AssetUploadStatus.Pending, asset.UploadStatus);
|
|
Assert.Equal(AssetSecurityScanStatus.Pending, asset.SecurityScanStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Tenant_admin_can_confirm_upload_and_mark_asset_verified()
|
|
{
|
|
var storage = new FakeObjectStorageService
|
|
{
|
|
MetadataSizeBytes = 2048,
|
|
MetadataChecksumSha256 = new string('a', 64)
|
|
};
|
|
await using var factory = new ApiTestFactory(objectStorageService: storage);
|
|
var seed = await SeedAdminAsync(factory);
|
|
var assetId = Guid.NewGuid();
|
|
await factory.SeedAsync(new ContentAsset
|
|
{
|
|
Id = assetId,
|
|
TenantId = seed.TenantId,
|
|
Title = "课程讲义",
|
|
FileName = "lesson.pdf",
|
|
StorageProvider = AssetStorageProvider.AliyunOss,
|
|
Bucket = "tenant-assets",
|
|
ObjectKey = $"{seed.TenantId:N}/assets/lesson.pdf",
|
|
MimeType = "application/pdf",
|
|
AssetType = ContentAssetType.Pdf,
|
|
UploadStatus = AssetUploadStatus.Pending,
|
|
SecurityScanStatus = AssetSecurityScanStatus.Pending
|
|
});
|
|
using var client = factory.CreateClient();
|
|
await LoginAsync(client, seed);
|
|
|
|
using var response = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/confirm",
|
|
new AssetUploadConfirmDto
|
|
{
|
|
AssetId = assetId,
|
|
MimeType = "application/pdf",
|
|
FileSizeBytes = 2048,
|
|
ChecksumSha256 = new string('a', 64)
|
|
});
|
|
var body = await ReadJsonAsync(response);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
|
Assert.Equal("Verified", body.RootElement.GetProperty("item").GetProperty("uploadStatus").GetString());
|
|
Assert.Equal(2048, body.RootElement.GetProperty("metadata").GetProperty("sizeBytes").GetInt64());
|
|
|
|
using var scope = factory.CreateSystemScope();
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
|
var asset = dbContext.ContentAssets.Single(asset => asset.Id == assetId);
|
|
Assert.Equal(AssetUploadStatus.Verified, asset.UploadStatus);
|
|
Assert.Equal(seed.UserId, asset.VerifiedBy);
|
|
Assert.Equal(2048, asset.VerifiedSizeBytes);
|
|
Assert.Equal(new string('a', 64), asset.VerifiedChecksumSha256);
|
|
Assert.False(dbContext.TenantFeatureUsages.Any(value =>
|
|
value.TenantId == seed.TenantId && value.MetricCode == SaasQuotaMetricCatalog.StorageBytes));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Verified_upload_consumes_storage_quota_and_archive_releases_it()
|
|
{
|
|
var storage = new FakeObjectStorageService { MetadataSizeBytes = 6 };
|
|
await using var factory = new ApiTestFactory(objectStorageService: storage);
|
|
var seed = await SeedAdminWithStorageQuotaAsync(factory, 10);
|
|
var firstAssetId = Guid.NewGuid();
|
|
var secondAssetId = Guid.NewGuid();
|
|
await factory.SeedAsync(
|
|
PendingAsset(firstAssetId, seed.TenantId, "first.pdf"),
|
|
PendingAsset(secondAssetId, seed.TenantId, "second.pdf"));
|
|
using var client = factory.CreateClient();
|
|
await LoginAsync(client, seed);
|
|
|
|
using var firstConfirm = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/confirm",
|
|
new AssetUploadConfirmDto { AssetId = firstAssetId });
|
|
Assert.Equal(HttpStatusCode.OK, firstConfirm.StatusCode);
|
|
Assert.Equal(6, await StorageUsageAsync(factory, seed.TenantId));
|
|
|
|
storage.MetadataSizeBytes = 5;
|
|
using var exhausted = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/confirm",
|
|
new AssetUploadConfirmDto { AssetId = secondAssetId });
|
|
Assert.Equal(HttpStatusCode.Conflict, exhausted.StatusCode);
|
|
var exhaustedBody = await ReadJsonAsync(exhausted);
|
|
Assert.Equal("feature_quota_exhausted", exhaustedBody.RootElement.GetProperty("code").GetString());
|
|
Assert.Equal(6, await StorageUsageAsync(factory, seed.TenantId));
|
|
|
|
using var archive = await client.DeleteAsync($"/api/tenant-content/assets/{firstAssetId}");
|
|
Assert.Equal(HttpStatusCode.OK, archive.StatusCode);
|
|
Assert.Equal(0, await StorageUsageAsync(factory, seed.TenantId));
|
|
|
|
using var secondConfirm = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/confirm",
|
|
new AssetUploadConfirmDto { AssetId = secondAssetId });
|
|
Assert.Equal(HttpStatusCode.OK, secondConfirm.StatusCode);
|
|
Assert.Equal(5, await StorageUsageAsync(factory, seed.TenantId));
|
|
|
|
using var repeatedConfirm = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/uploads/confirm",
|
|
new AssetUploadConfirmDto { AssetId = secondAssetId });
|
|
Assert.Equal(HttpStatusCode.OK, repeatedConfirm.StatusCode);
|
|
Assert.Equal(5, await StorageUsageAsync(factory, seed.TenantId));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Import_jobs_are_scoped_to_current_tenant_and_include_detail()
|
|
{
|
|
await using var factory = new ApiTestFactory(objectStorageService: new FakeObjectStorageService());
|
|
var seed = await SeedAdminAsync(factory);
|
|
var otherTenantId = Guid.NewGuid();
|
|
var jobId = Guid.NewGuid();
|
|
var itemId = Guid.NewGuid();
|
|
await factory.SeedAsync(
|
|
new Tenant
|
|
{
|
|
Id = otherTenantId,
|
|
Slug = otherTenantId.ToString("N"),
|
|
Name = "Other Tenant",
|
|
Status = TenantStatus.Active,
|
|
Metadata = JsonDefaults.Object()
|
|
},
|
|
new ContentImportJob
|
|
{
|
|
Id = jobId,
|
|
TenantId = seed.TenantId,
|
|
SourceName = "questions.xlsx",
|
|
ImportType = ContentImportType.Questions,
|
|
SourceFormat = ImportSourceFormat.Excel,
|
|
Status = ContentImportStatus.CompletedWithErrors,
|
|
TotalCount = 1,
|
|
ErrorCount = 1
|
|
},
|
|
new ContentImportItem
|
|
{
|
|
Id = itemId,
|
|
TenantId = seed.TenantId,
|
|
JobId = jobId,
|
|
RowNo = 1,
|
|
Status = ContentImportItemStatus.Invalid,
|
|
IssuesCount = 1
|
|
},
|
|
new ContentImportIssue
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = seed.TenantId,
|
|
JobId = jobId,
|
|
ItemId = itemId,
|
|
RowNo = 1,
|
|
Severity = ImportIssueSeverity.Error,
|
|
Code = "missing_answer",
|
|
Message = "答案不能为空"
|
|
},
|
|
new ContentImportJob
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
TenantId = otherTenantId,
|
|
SourceName = "other.xlsx"
|
|
});
|
|
using var client = factory.CreateClient();
|
|
await LoginAsync(client, seed);
|
|
|
|
using var listResponse = await client.GetAsync("/api/tenant-content/import-jobs?status=completedWithErrors");
|
|
using var detailResponse = await client.GetAsync($"/api/tenant-content/import-jobs/{jobId}");
|
|
var list = await ReadJsonAsync(listResponse);
|
|
var detail = await ReadJsonAsync(detailResponse);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
|
|
var listItem = Assert.Single(list.RootElement.GetProperty("items").EnumerateArray());
|
|
Assert.Equal(jobId, listItem.GetProperty("id").GetGuid());
|
|
Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
|
|
Assert.Equal(jobId, detail.RootElement.GetProperty("job").GetProperty("id").GetGuid());
|
|
Assert.Single(detail.RootElement.GetProperty("items").EnumerateArray());
|
|
Assert.Single(detail.RootElement.GetProperty("issues").EnumerateArray());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Tenant_admin_can_upsert_asset_sign_access_and_query_asset_events()
|
|
{
|
|
await using var factory = new ApiTestFactory(objectStorageService: new FakeObjectStorageService());
|
|
var seed = await SeedAdminAsync(factory);
|
|
using var client = factory.CreateClient();
|
|
await LoginAsync(client, seed);
|
|
|
|
var upsertResponse = await client.PutAsJsonAsync(
|
|
"/api/tenant-content/assets",
|
|
new UpsertAssetDto
|
|
{
|
|
Title = "管理侧资料",
|
|
FileName = "admin.pdf",
|
|
AssetType = "pdf",
|
|
Visibility = "members",
|
|
Provider = "local_dev",
|
|
Bucket = "tenant-assets",
|
|
ObjectKey = $"{seed.TenantId:N}/admin.pdf",
|
|
PreviewObjectKey = $"{seed.TenantId:N}/admin-preview.pdf",
|
|
MimeType = "application/pdf",
|
|
FileSizeBytes = 512
|
|
});
|
|
var upsert = await ReadJsonAsync(upsertResponse);
|
|
var assetId = upsert.RootElement.GetProperty("item").GetProperty("id").GetGuid();
|
|
await factory.SeedAsync(new ContentAssetSecurityScanEvent
|
|
{
|
|
TenantId = seed.TenantId,
|
|
AssetId = assetId,
|
|
Provider = "local",
|
|
ScanStatus = AssetSecurityScanStatus.Passed,
|
|
RiskLevel = AssetSecurityRiskLevel.None
|
|
});
|
|
|
|
var downloadResponse = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/sign-download",
|
|
new AssetAccessSignDto { AssetId = assetId, ExpiresInSeconds = 120 });
|
|
var previewResponse = await client.PostAsJsonAsync(
|
|
"/api/tenant-content/assets/sign-preview",
|
|
new AssetAccessSignDto { AssetId = assetId, ExpiresInSeconds = 120 });
|
|
var accessEventsResponse = await client.GetAsync($"/api/tenant-content/assets/access-events?assetId={assetId}");
|
|
var scanEventsResponse = await client.GetAsync($"/api/tenant-content/assets/security-scan-events?assetId={assetId}");
|
|
var accessEvents = await ReadJsonAsync(accessEventsResponse);
|
|
var scanEvents = await ReadJsonAsync(scanEventsResponse);
|
|
|
|
Assert.Equal(HttpStatusCode.OK, upsertResponse.StatusCode);
|
|
Assert.Equal(HttpStatusCode.OK, downloadResponse.StatusCode);
|
|
Assert.Equal(HttpStatusCode.OK, previewResponse.StatusCode);
|
|
Assert.Equal(2, accessEvents.RootElement.GetProperty("items").GetArrayLength());
|
|
Assert.Single(scanEvents.RootElement.GetProperty("items").EnumerateArray());
|
|
}
|
|
|
|
private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedAdminAsync(ApiTestFactory factory)
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var phone = "13900000000";
|
|
await factory.SeedAsync(
|
|
new Tenant
|
|
{
|
|
Id = tenantId,
|
|
Slug = tenantId.ToString("N"),
|
|
Name = "Test Tenant",
|
|
Status = TenantStatus.Active,
|
|
Metadata = JsonDefaults.Object()
|
|
},
|
|
new User
|
|
{
|
|
Id = userId,
|
|
Phone = phone,
|
|
Name = "Tenant Admin"
|
|
}.WithTestPassword(),
|
|
new TenantMembership
|
|
{
|
|
TenantId = tenantId,
|
|
UserId = userId,
|
|
Role = TenantRole.TenantAdmin,
|
|
Status = MembershipStatus.Active
|
|
});
|
|
|
|
return (tenantId, userId, phone);
|
|
}
|
|
|
|
private static async Task<(Guid TenantId, Guid UserId, string Phone)> SeedAdminWithStorageQuotaAsync(
|
|
ApiTestFactory factory,
|
|
long storageLimit)
|
|
{
|
|
var tenantId = Guid.NewGuid();
|
|
var userId = Guid.NewGuid();
|
|
var phone = "13900000001";
|
|
var offeringId = Guid.NewGuid();
|
|
var versionId = Guid.NewGuid();
|
|
var subscriptionId = Guid.NewGuid();
|
|
var now = DateTimeOffset.UtcNow;
|
|
await factory.SeedAsync(
|
|
new Tenant
|
|
{
|
|
Id = tenantId,
|
|
Slug = tenantId.ToString("N"),
|
|
Name = "Storage Quota Tenant",
|
|
Status = TenantStatus.Active,
|
|
BillingStatus = BillingStatus.Active
|
|
},
|
|
new User
|
|
{
|
|
Id = userId,
|
|
Phone = phone,
|
|
Name = "Storage Quota Admin"
|
|
}.WithTestPassword(),
|
|
new TenantMembership
|
|
{
|
|
TenantId = tenantId,
|
|
UserId = userId,
|
|
Role = TenantRole.TenantAdmin,
|
|
Status = MembershipStatus.Active
|
|
},
|
|
new SaasFeature
|
|
{
|
|
Code = SaasFeatureCatalog.PrivateQuestionBank,
|
|
Name = "Private question bank",
|
|
Category = "content",
|
|
Status = SaasFeatureStatus.Active
|
|
},
|
|
new SaasOffering
|
|
{
|
|
Id = offeringId,
|
|
Code = $"storage-quota-{tenantId:N}",
|
|
Name = "Storage Quota Plan",
|
|
Type = SaasOfferingType.BasePlan,
|
|
Status = SaasOfferingStatus.Active
|
|
},
|
|
new SaasOfferingVersion
|
|
{
|
|
Id = versionId,
|
|
OfferingId = offeringId,
|
|
Version = 1,
|
|
Status = SaasOfferingVersionStatus.Draft,
|
|
BillingCycle = PlatformBillingCycle.Monthly,
|
|
OriginalAmountCents = 100,
|
|
AmountCents = 100,
|
|
EffectiveAt = now.AddDays(-1)
|
|
},
|
|
new SaasOfferingVersionFeature
|
|
{
|
|
OfferingVersionId = versionId,
|
|
FeatureCode = SaasFeatureCatalog.PrivateQuestionBank
|
|
},
|
|
new SaasFeatureLimitDefinition
|
|
{
|
|
MetricCode = SaasQuotaMetricCatalog.StorageBytes,
|
|
FeatureCode = SaasFeatureCatalog.PrivateQuestionBank,
|
|
Name = "Storage bytes",
|
|
Unit = "byte",
|
|
Kind = SaasFeatureLimitKind.Current,
|
|
WarningPercent = 80,
|
|
IsHardLimit = true
|
|
},
|
|
new SaasOfferingVersionLimit
|
|
{
|
|
OfferingVersionId = versionId,
|
|
MetricCode = SaasQuotaMetricCatalog.StorageBytes,
|
|
LimitValue = storageLimit
|
|
},
|
|
new TenantSaasSubscription
|
|
{
|
|
Id = subscriptionId,
|
|
TenantId = tenantId,
|
|
BaseOfferingVersionId = versionId,
|
|
Status = TenantSaasSubscriptionStatus.Active,
|
|
StartsAt = now.AddDays(-1),
|
|
CurrentPeriodStart = now.AddDays(-1),
|
|
CurrentPeriodEnd = now.AddMonths(1)
|
|
},
|
|
new TenantSaasSubscriptionItem
|
|
{
|
|
TenantId = tenantId,
|
|
SubscriptionId = subscriptionId,
|
|
OfferingVersionId = versionId,
|
|
ItemType = TenantSaasSubscriptionItemType.BasePlan,
|
|
Status = TenantSaasSubscriptionItemStatus.Active,
|
|
StartsAt = now.AddDays(-1),
|
|
EndsAt = now.AddMonths(1)
|
|
});
|
|
|
|
using var scope = factory.CreateSystemScope("Publish storage quota fixture");
|
|
var dbContext = scope.ServiceProvider.GetRequiredService<TikuDbContext>();
|
|
var version = await dbContext.SaasOfferingVersions.SingleAsync(value => value.Id == versionId);
|
|
version.Status = SaasOfferingVersionStatus.Published;
|
|
version.PublishedAt = now;
|
|
await dbContext.SaveChangesAsync();
|
|
return (tenantId, userId, phone);
|
|
}
|
|
|
|
private static ContentAsset PendingAsset(Guid id, Guid tenantId, string fileName) => new()
|
|
{
|
|
Id = id,
|
|
TenantId = tenantId,
|
|
Title = fileName,
|
|
FileName = fileName,
|
|
StorageProvider = AssetStorageProvider.AliyunOss,
|
|
Bucket = "tenant-assets",
|
|
ObjectKey = $"{tenantId:N}/assets/{fileName}",
|
|
MimeType = "application/pdf",
|
|
AssetType = ContentAssetType.Pdf,
|
|
UploadStatus = AssetUploadStatus.Pending,
|
|
SecurityScanStatus = AssetSecurityScanStatus.Pending
|
|
};
|
|
|
|
private static async Task<long> StorageUsageAsync(ApiTestFactory factory, Guid tenantId)
|
|
{
|
|
using var scope = factory.CreateSystemScope("Read storage quota usage");
|
|
return await scope.ServiceProvider.GetRequiredService<TikuDbContext>().TenantFeatureUsages
|
|
.Where(value => value.TenantId == tenantId && value.MetricCode == SaasQuotaMetricCatalog.StorageBytes)
|
|
.Select(value => value.UsedValue)
|
|
.SingleOrDefaultAsync();
|
|
}
|
|
|
|
private static async Task LoginAsync(
|
|
HttpClient client,
|
|
(Guid TenantId, Guid UserId, string Phone) seed)
|
|
{
|
|
client.UseAccessToken(await client.LoginAsTenantAsync(seed.TenantId, seed.Phone));
|
|
}
|
|
|
|
private static async Task<JsonDocument> ReadJsonAsync(HttpResponseMessage response)
|
|
{
|
|
var stream = await response.Content.ReadAsStreamAsync();
|
|
return await JsonDocument.ParseAsync(stream);
|
|
}
|
|
|
|
private sealed class FakeObjectStorageService : IObjectStorageService
|
|
{
|
|
public long? MetadataSizeBytes { get; set; }
|
|
|
|
public string? MetadataChecksumSha256 { get; init; }
|
|
|
|
public string ConfiguredDefaultProvider() => ObjectStorageProviders.AliyunOss;
|
|
|
|
public string ConfiguredDefaultBucket() => "tenant-assets";
|
|
|
|
public string NormalizeProvider(string? value, string? fallback = null)
|
|
{
|
|
return value ?? fallback ?? ObjectStorageProviders.AliyunOss;
|
|
}
|
|
|
|
public string ValidateObjectKey(Guid tenantId, string objectKey) => objectKey;
|
|
|
|
public string ValidateMimeType(string mimeType) => mimeType;
|
|
|
|
public long? ValidateFileSize(long? fileSizeBytes) => fileSizeBytes;
|
|
|
|
public void AssertUploadProvider(string provider) { }
|
|
|
|
public void AssertWritableLocation(StorageAssetLocation location) { }
|
|
|
|
public Task<ObjectStorageSignedUrl> SignUploadAsync(
|
|
ObjectStorageUploadSignRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(new ObjectStorageSignedUrl(
|
|
request.Provider,
|
|
request.Bucket,
|
|
request.ObjectKey,
|
|
"PUT",
|
|
new Uri($"https://storage.example.test/{request.ObjectKey}"),
|
|
new Dictionary<string, string> { ["content-type"] = request.MimeType },
|
|
DateTimeOffset.UtcNow.Add(request.ExpiresIn),
|
|
request.ExpiresIn,
|
|
"fake-signed-url"));
|
|
}
|
|
|
|
public Task<ObjectStorageSignedUrl> SignDownloadAsync(
|
|
ObjectStorageDownloadSignRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(new ObjectStorageSignedUrl(
|
|
request.Provider,
|
|
request.Bucket,
|
|
request.ObjectKey,
|
|
"GET",
|
|
new Uri($"https://storage.example.test/{request.ObjectKey}"),
|
|
new Dictionary<string, string>(),
|
|
DateTimeOffset.UtcNow.Add(request.ExpiresIn),
|
|
request.ExpiresIn,
|
|
"fake-signed-url"));
|
|
}
|
|
|
|
public Task<ObjectStorageWriteResult> WriteObjectAsync(
|
|
ObjectStorageWriteRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(new ObjectStorageWriteResult(
|
|
request.Provider,
|
|
request.Bucket,
|
|
request.ObjectKey,
|
|
new Uri($"https://storage.example.test/{request.ObjectKey}"),
|
|
request.FileSizeBytes,
|
|
request.MimeType,
|
|
request.ChecksumSha256,
|
|
"fake-etag",
|
|
new Dictionary<string, string>(),
|
|
"fake-write"));
|
|
}
|
|
|
|
public Task<ObjectStorageMetadata> HeadObjectAsync(
|
|
ObjectStorageHeadRequest request,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return Task.FromResult(new ObjectStorageMetadata(
|
|
request.Provider,
|
|
request.Bucket,
|
|
request.ObjectKey,
|
|
Exists: true,
|
|
MetadataSizeBytes ?? request.DeclaredFileSizeBytes,
|
|
request.DeclaredMimeType,
|
|
MetadataChecksumSha256 ?? request.DeclaredChecksumSha256,
|
|
"etag",
|
|
DateTimeOffset.UtcNow.ToString("O"),
|
|
new Dictionary<string, string>(),
|
|
"fake-head"));
|
|
}
|
|
}
|
|
}
|