Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,4 @@
<PackageVersion Include="coverlet.msbuild" Version="6.0.4" />
<PackageVersion Include="ZstdSharp.Port" Version="0.8.6" />
</ItemGroup>
</Project>
</Project>
67 changes: 67 additions & 0 deletions src/GZCTF.Integration.Test/Tests/Api/AccountMetadataTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Net.Http.Json;
using System.Text.Json;
using GZCTF.Integration.Test.Base;
using GZCTF.Models.Data;
using GZCTF.Models.Request.Account;
using GZCTF.Repositories.Interface;
using GZCTF.Utils;
using Microsoft.Extensions.DependencyInjection;
using Xunit;

namespace GZCTF.Integration.Test.Tests.Api;

/// <summary>
/// Tests for account metadata update flow
/// </summary>
[Collection(nameof(IntegrationTestCollection))]
public class AccountMetadataTests(GZCTFApplicationFactory factory)
{
[Fact]
public async Task Update_Persists_Metadata_To_Profile()
{
const string password = "P@ssw0rd!";
var userName = TestDataSeeder.RandomName();
var email = $"{userName}@example.com";
var seededUser = await TestDataSeeder.CreateUserAsync(factory.Services, userName, password, email);

var metadataKey = $"meta_{Guid.NewGuid():N}";
var metadataValue = "2024-XYZ";

using (var scope = factory.Services.CreateScope())
{
var metadataRepo = scope.ServiceProvider.GetRequiredService<IUserMetadataFieldRepository>();
await metadataRepo.CreateAsync(new UserMetadataField
{
Key = metadataKey,
DisplayName = "Metadata Field",
Type = UserMetadataFieldType.Text,
Required = true,
Order = 1
});
}

using var client = factory.CreateClient();

var loginResponse = await client.PostAsJsonAsync("/api/Account/LogIn",
new LoginModel { UserName = seededUser.UserName, Password = password });
loginResponse.EnsureSuccessStatusCode();

var updateResponse = await client.PutAsJsonAsync("/api/Account/Update",
new ProfileUpdateModel
{
Metadata = new SortedDictionary<string, JsonDocument?>
{
[metadataKey] = JsonSerializer.SerializeToDocument(metadataValue)
}
});
updateResponse.EnsureSuccessStatusCode();

var profileResponse = await client.GetAsync("/api/Account/Profile");
profileResponse.EnsureSuccessStatusCode();

var profile = await profileResponse.Content.ReadFromJsonAsync<ProfileUserInfoModel>();
Assert.NotNull(profile?.Metadata);
Assert.True(profile.Metadata!.TryGetValue(metadataKey, out var value));
Assert.Equal(metadataValue, value?.RootElement.GetString());
}
}
85 changes: 85 additions & 0 deletions src/GZCTF.Test/UnitTests/Models/UserInfoTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Collections.Generic;
using System.Text.Json;
using GZCTF.Models.Data;
using GZCTF.Models.Request.Account;
using GZCTF.Models.Request.Admin;
using Xunit;

namespace GZCTF.Test.UnitTests.Models;

public class UserInfoTests
{
[Fact]
public void UpdateUserInfo_ProfileUpdateModel_UpdatesBioAndPhone()
{
var user = new UserInfo();
var model = new ProfileUpdateModel
{
Bio = "new bio",
Phone = "new phone",
Metadata = new SortedDictionary<string, JsonDocument?>
{
["key1"] = JsonSerializer.Deserialize<JsonDocument>("\"value1\"")
}
};

user.UpdateUserInfo(model);

Assert.Equal("new bio", user.Bio);
Assert.Equal("new phone", user.PhoneNumber);
}

[Fact]
public void UpdateUserInfo_AdminUserInfoModel_UpdatesMetadata()
{
var user = new UserInfo();
var metadata = new SortedDictionary<string, JsonDocument?>
{
["key2"] = JsonSerializer.Deserialize<JsonDocument>("\"value2\"")
};
var model = new AdminUserInfoModel { Metadata = metadata };

user.UpdateUserInfo(model);

Assert.NotNull(user.Metadata);
Assert.Equal("value2", user.Metadata["key2"]?.RootElement.GetString());
}

[Fact]
public void UpdateUserInfo_UserCreateModel_UpdatesMetadata()
{
var user = new UserInfo();
var metadata = new SortedDictionary<string, JsonDocument?>
{
["key3"] = JsonSerializer.Deserialize<JsonDocument>("\"value3\"")
};
var model = new UserCreateModel
{
UserName = "test",
Password = "password",
Email = "[email protected]",
Metadata = metadata
};

user.UpdateUserInfo(model);

Assert.NotNull(user.Metadata);
Assert.Equal("value3", user.Metadata["key3"]?.RootElement.GetString());
}

[Fact]
public void UpdateUserInfo_ProfileUpdateModel_NullMetadata_DoesNotUpdate()
{
var initialMetadata = new SortedDictionary<string, JsonDocument?>
{
["key1"] = JsonSerializer.Deserialize<JsonDocument>("\"initial\"")
};
var user = new UserInfo { Metadata = initialMetadata };
var model = new ProfileUpdateModel { Metadata = null };

user.UpdateUserInfo(model);

Assert.NotNull(user.Metadata);
Assert.Equal("initial", user.Metadata["key1"]?.RootElement.GetString());
}
}
126 changes: 126 additions & 0 deletions src/GZCTF.Test/UnitTests/Models/UserMetadataFieldValidationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System.Text.Json;
using GZCTF.Extensions;
using GZCTF.Models.Data;
using GZCTF.Utils;
using Microsoft.Extensions.Localization;
using Xunit;

namespace GZCTF.Test.UnitTests.Models;

public class UserMetadataFieldValidationTests
{
private readonly IStringLocalizer<Program> _localizer = Server.StaticLocalizer;

[Fact]
public void Validate_RequiredField_NullValue_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Required = true, Type = UserMetadataFieldType.Text };
var value = JsonSerializer.Deserialize<JsonDocument>("null");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Field is required", error);
}

[Fact]
public void Validate_RequiredField_EmptyString_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Required = true, Type = UserMetadataFieldType.Text };
var value = JsonSerializer.Deserialize<JsonDocument>("\"\"");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Field is required", error);
}

[Fact]
public void Validate_TextField_MaxLength_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Text, MaxLength = 5 };
var value = JsonSerializer.Deserialize<JsonDocument>("\"123456\"");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Value exceeds maximum length of 5", error);
}

[Fact]
public void Validate_TextField_Pattern_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Text, Pattern = "^[0-9]+$" };
var value = JsonSerializer.Deserialize<JsonDocument>("\"abc\"");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Value does not match the required pattern", error);
}

[Fact]
public void Validate_NumberField_MinValue_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Number, MinValue = 10 };
var value = JsonSerializer.Deserialize<JsonDocument>("5");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Value must be at least 10", error);
}

[Fact]
public void Validate_NumberField_MaxValue_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Number, MaxValue = 10 };
var value = JsonSerializer.Deserialize<JsonDocument>("15");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Value must be at most 10", error);
}

[Fact]
public void Validate_SelectField_InvalidOption_ReturnsFalse()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Select, Options = ["A", "B"] };
var value = JsonSerializer.Deserialize<JsonDocument>("\"C\"");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Invalid option selected", error);
}

[Fact]
public void Validate_MultiSelectField_InvalidOption_ReturnsFalse()
{
var field = new UserMetadataField
{
Key = "test",
Type = UserMetadataFieldType.MultiSelect,
Options = ["A", "B"]
};
var value = JsonSerializer.Deserialize<JsonDocument>("[\"A\", \"C\"]");

var result = field.Validate(value, _localizer, out var error);

Assert.False(result);
Assert.Equal("Invalid option selected: C", error);
}

[Fact]
public void Validate_ValidInput_ReturnsTrue()
{
var field = new UserMetadataField { Key = "test", Type = UserMetadataFieldType.Text, Required = true };
var value = JsonSerializer.Deserialize<JsonDocument>("\"valid\"");

var result = field.Validate(value, _localizer, out var error);

Assert.True(result);
Assert.Null(error);
}
}
2 changes: 1 addition & 1 deletion src/GZCTF.Test/UnitTests/Transfer/TransferValidatorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ public void ValidateChallenge_InvalidScoring_ShouldThrow()

var ex = Assert.Throws<InvalidOperationException>(() =>
TransferValidator.ValidateRecursive(challenge, "Challenge"));
Assert.Contains("Original score must be positive", ex.Message);
Assert.Contains("Original score must be non-negative", ex.Message);
}

[Fact]
Expand Down
Loading
Loading