using System.Net; using System.Net.Http.Json; using System.Text.Json; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using TheCr3w.Shared.Crypto; using TheCr3w.Shared.Data; using TheCr3w.Shared.Models; using TheCr3w.Shared.Storage; using Xunit; namespace TheCr3w.Tests; public class TestR2StorageService : IR2StorageService { public string GeneratePreSignedDownloadUrl(string objectKey, TimeSpan? duration = null) { return $"https://bf705752e38169fedb83efd5b747390e.r2.cloudflarestorage.com/thecr3w-app/{objectKey}?token=mock-sigv4-token"; } public string GeneratePreSignedUploadUrl(string objectKey, string contentType, TimeSpan? duration = null) { return $"https://bf705752e38169fedb83efd5b747390e.r2.cloudflarestorage.com/thecr3w-app/{objectKey}?upload=true"; } public Task ObjectExistsAsync(string objectKey, CancellationToken cancellationToken = default) { return Task.FromResult(true); } } public class NativeUpdateApiFactory : WebApplicationFactory { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.UseEnvironment("Testing"); builder.ConfigureServices(services => { services.AddDbContext(options => { options.UseInMemoryDatabase("TheCr3w_Test_Db"); }); // Podmiana R2 na mock testowy var r2Descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(IR2StorageService)); if (r2Descriptor != null) services.Remove(r2Descriptor); services.AddSingleton(); }); } } public class ApiIntegrationTests : IClassFixture { private readonly HttpClient _client; private readonly NativeUpdateApiFactory _factory; public ApiIntegrationTests(NativeUpdateApiFactory factory) { _factory = factory; _client = factory.CreateClient(); } [Fact] public async Task HealthEndpoint_ShouldReturnHealthy() { var response = await _client.GetAsync("/health"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); Assert.Contains("Healthy", body); } [Fact] public async Task GetSigningPublicKey_ShouldReturnValidEd25519Key() { var response = await _client.GetAsync("/api/v1/keys/signing-public-key"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var json = await response.Content.ReadFromJsonAsync(); Assert.Equal("Ed25519", json.GetProperty("algorithm").GetString()); var keyHex = json.GetProperty("public_key_hex").GetString(); Assert.NotNull(keyHex); Assert.Equal(64, keyHex.Length); } [Fact] public async Task CheckUpdate_WhenUpToDate_ShouldReturnFalse() { var response = await _client.GetAsync("/api/v1/updates/check?app_id=com.thecr3w.uptodate¤t_version=1.0.0&version_code=10&platform=android&architecture=arm64-v8a&channel=stable"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var json = await response.Content.ReadFromJsonAsync(); Assert.False(json.GetProperty("update_available").GetBoolean()); } [Fact] public async Task CheckUpdate_WhenNewVersionPublished_ShouldReturnUpdateAndPresignedUrl() { var appId = "com.thecr3w.needsupdate"; using (var scope = _factory.Services.CreateScope()) { var db = scope.ServiceProvider.GetRequiredService(); var (priv, pub) = Ed25519Service.GenerateKeyPair(); var newRelease = new ReleaseEntity { ReleaseId = Guid.NewGuid(), AppId = appId, Version = "2.0.0", VersionCode = 20, Platform = "android", Architecture = "arm64-v8a", Channel = "stable", Status = "published", Mandatory = true, MinimumSupportedVersion = "1.5.0", FileSizeBytes = 25000000, Sha256 = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9", R2ObjectKey = "apps/com.thecr3w.needsupdate/android/stable/2.0.0_20_arm64-v8a.apk", SignatureEd25519 = "mock_ed25519_signature", PublishedAt = DateTimeOffset.UtcNow, CreatedBy = "release-bot" }; db.Releases.Add(newRelease); await db.SaveChangesAsync(); } var response = await _client.GetAsync($"/api/v1/updates/check?app_id={appId}¤t_version=1.0.0&version_code=10&platform=android&architecture=arm64-v8a&channel=stable"); Assert.Equal(HttpStatusCode.OK, response.StatusCode); var json = await response.Content.ReadFromJsonAsync(); Assert.True(json.GetProperty("update_available").GetBoolean()); Assert.Equal("2.0.0", json.GetProperty("latest_version").GetString()); Assert.Equal(20, json.GetProperty("version_code").GetInt32()); Assert.True(json.GetProperty("mandatory").GetBoolean()); var downloadUrl = json.GetProperty("download_url").GetString(); Assert.NotNull(downloadUrl); Assert.Contains("bf705752e38169fedb83efd5b747390e.r2.cloudflarestorage.com", downloadUrl); Assert.Contains("thecr3w-app", downloadUrl); } }