Finit test EF il manque ceux de l'API et de mon Manager
continuous-integration/drone/push Build is passing Details

master
Louis DUFOUR 2 years ago
parent 112425d235
commit d6723ed2b6

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 238 KiB

@ -12,12 +12,12 @@ namespace API.Controllers
public class SkinController : ControllerBase
{
// private readonly ManagerData data;
private readonly StubData data;
private readonly ManagerData data;
// private readonly StubData data;
private readonly ILogger<SkinController> _logger;
public SkinController(StubData manager, ILogger<SkinController> logger)
public SkinController(ManagerData manager, ILogger<SkinController> logger)
{
data = manager;
_logger = logger;

@ -17,12 +17,12 @@ namespace API.Controllers.version1
public class ChampionController : ControllerBase
{
// private readonly ManagerData data;
private readonly StubData data;
private readonly ManagerData data;
// private readonly StubData data;
private readonly ILogger<ChampionController> _logger;
public ChampionController(StubData manager, ILogger<ChampionController> logger)
public ChampionController(ManagerData manager, ILogger<ChampionController> logger)
{
data = manager;
_logger = logger;

@ -1,5 +1,6 @@
using API.Dto;
using API.Mapping;
using EFManager;
using Microsoft.AspNetCore.Mvc;
using Model;
using StubLib;
@ -11,12 +12,12 @@ namespace API.Controllers.version2
[Route("api/v{version:ApiVersion}/[controller]")]
public class ChampionController : ControllerBase
{
// private readonly ManagerData data;
private readonly StubData data;
private readonly ManagerData data;
// private readonly StubData data;
private readonly ILogger<ChampionController> _logger;
public ChampionController(StubData manager, ILogger<ChampionController> logger)
public ChampionController(ManagerData manager, ILogger<ChampionController> logger)
{
data = manager;
_logger = logger;

@ -39,7 +39,11 @@ namespace API.Mapping
if (DtoChamp.Skills != null) foreach (var skill in DtoChamp.Skills) { champion.AddSkill(skill.toModel()); }
champion.AddCharacteristics((Tuple<string, int>)DtoChamp.NameCharac.Zip(DtoChamp.ValueCharac, (name, value) => Tuple.Create(name, value)));
var characteristics = DtoChamp.NameCharac.Zip(DtoChamp.ValueCharac, (name, value) => Tuple.Create(name, value));
foreach (var characteristic in characteristics)
{
champion.AddCharacteristics(characteristic);
}
return champion;
}

@ -16,8 +16,8 @@ builder.Services.AddDbContext<SQLiteContext>(options => options.UseSqlite(connec
builder.Services.AddSingleton<ManagerData>();
builder.Services.AddScoped<StubData>();
// builder.Services.AddScoped<ManagerData>();
// builder.Services.AddScoped<StubData>();
builder.Services.AddScoped<ManagerData>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();

Binary file not shown.

@ -33,7 +33,7 @@ namespace EFMapping
public static Champion ToModel(this EFChampion EFChamp)
{
var champion = new Champion(EFChamp.Name, EFChamp.Class, EFChamp.Icon, EFChamp.Image.Base64, EFChamp.Bio);
var champion = new Champion(EFChamp.Name, EFChamp.Class, EFChamp.Icon, EFChamp.Image.ToModel().Base64, EFChamp.Bio);
if (EFChamp.Skills != null) foreach (var skill in EFChamp.Skills) { champion.AddSkill(skill.ToModel()); }
if (EFChamp.Characteristics != null) foreach (var charac in EFChamp.Characteristics) { champion.AddCharacteristics(charac.ToModel()); }
return champion;

@ -6,6 +6,7 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace EFMapping
{
@ -13,7 +14,8 @@ namespace EFMapping
{
public static EFCharacteristics ToEF(this KeyValuePair<string, int> item, EFChampion champion, SQLiteContext context)
{
var EfCharacteristics = context.Characteristics.Find(item.Key, champion.Name);
var EfCharacteristics = context.Characteristics.FirstOrDefault(c => c.Name == item.Key && c.Champion.Name == champion.Name);
if (EfCharacteristics == null)
{
return new()

@ -11,6 +11,8 @@
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="NSubstitute" Version="5.0.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -22,4 +24,8 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\API\API.csproj" />
</ItemGroup>
</Project>

@ -1,11 +1,8 @@
namespace TestAPI
{
public class UnitTestChampionController
{
[Fact]
public void Test1()
{
}
}
}

@ -1,4 +1,10 @@
using System;
using API.Controllers;
using API.Dto;
using Microsoft.AspNetCore.Mvc;
using Model;
using Moq;
using StubLib;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -6,7 +12,8 @@ using System.Threading.Tasks;
namespace TestAPI
{
internal class UnitTestSkinController
public class UnitTestSkinController
{
}
}

@ -54,7 +54,7 @@ namespace TestEF
NameChampion = "Aatrox",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
} } ),
} }),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
@ -84,129 +84,202 @@ namespace TestEF
}
}
[Fact]
public async Task GetChampion_Test()
public async Task UpdateChampion_Test()
{
//connection must be opened to use In-memory database
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource =:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new StubEFChampions(options))
using (var context = new SQLiteContext(options))
{
//context.Database.OpenConnection();
await context.Database.EnsureCreatedAsync(); //pour créer la base si elle n'existe pas déjà
await context.Database.EnsureCreatedAsync(); // pour créer la base si elle n'existe pas déjà
var champs = context.Champions.SingleOrDefault(c => c.Name == "Akali");
// Arrange
var champion = new EFChampion
{
Name = "Champion 1",
Bio = "Bio Champion 1",
Icon = "aatrox_icon.jpg",
Class = ChampionClass.Assassin,
Assert.NotNull(champs);
Assert.Equal("Akali", champs.Name);
}
}
}
Characteristics = new List<EFCharacteristics> {
new EFCharacteristics { Name = "Attack Damage", Value = 60 },
new EFCharacteristics { Name = "Armor", Value = 38 },
new EFCharacteristics { Name = "Magic Resist", Value = 32 }
},
Skills = ImmutableHashSet.Create<EFSkill>(
new EFSkill { Name = "Skill 1", Description = "Desc Skill 1" },
new EFSkill { Name = "Skill 2", Description = "Desc Skill 2" }
),
Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin> {
new EFSkin
{
Name = "Mecha Kingdoms Aatrox",
Description = "In a distant cyberfuture, the champion Aatrox becomes a symbol of ultimate power and glory...",
Icon = "aatrox_mecha_icon.jpg",
Price = 1350,
NameChampion = "Aatrox",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
} }),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
context.Add(champion);
await context.SaveChangesAsync();
champion.Bio = "New Bio"; // Updating the bio of the champion
// Act
await context.SaveChangesAsync();
var updatedChampion = await context.Champions.FirstOrDefaultAsync(c => c.Name == "Champion 1");
// Assert
Assert.NotNull(updatedChampion);
Assert.Equal("New Bio", updatedChampion.Bio);
}
}
/*
[Fact]
public void Modify_Test()
public async Task DeleteChampion_Test()
{
//connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource=:memory:");
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource =:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
//prepares the database with one instance of the context
using (var context = new StubEFChampions(options))
{
//context.Database.OpenConnection();
context.Database.EnsureCreated();
/*
Nounours chewie = new Nounours { Nom = "Chewbacca" };
Nounours yoda = new Nounours { Nom = "Yoda" };
Nounours ewok = new Nounours { Nom = "Ewok" };
context.Nounours.Add(chewie);
context.Nounours.Add(yoda);
context.Nounours.Add(ewok);
context.SaveChanges();
}*/
//uses another instance of the context to do the tests
/* using (var context = new StubEFChampions(options))
{
context.Database.EnsureCreated();
string nameToFind = "ew";
Assert.Equal(2, context.Champions.Where(n => n.Name.ToLower().Contains(nameToFind)).Count());
nameToFind = "wo";
Assert.Equal(1, context.Champions.Where(n => n.Name.ToLower().Contains(nameToFind)).Count());
var ewok = context.Champions.Where(n => n.Name.ToLower().Contains(nameToFind)).First();
ewok.Name = "Wicket";
context.SaveChanges();
}
//uses another instance of the context to do the tests
using (var context = new StubEFChampions(options))
using (var context = new SQLiteContext(options))
{
context.Database.EnsureCreated();
await context.Database.EnsureCreatedAsync(); //pour créer la base si elle n'existe pas déjà
string nameToFind = "ew";
Assert.Equal(1, context.Champions.Where(n => n.Name.ToLower().Contains(nameToFind)).Count());
nameToFind = "wick";
Assert.Equal(1, context.Champions.Where(n => n.Name.ToLower().Contains(nameToFind)).Count());
}
}*/
/*
[SetUp]
public void Setup()
// Arrange
var champion = new EFChampion
{
}
Name = "ChampionToDelete",
Bio = "Bio Champion to delete",
Icon = "delete_icon.jpg",
Class = ChampionClass.Fighter,
Characteristics = new List<EFCharacteristics> {
new EFCharacteristics { Name = "Attack Damage", Value = 70 },
new EFCharacteristics { Name = "Armor", Value = 42 },
new EFCharacteristics { Name = "Magic Resist", Value = 28 }
},
Skills = ImmutableHashSet.Create<EFSkill>(
new EFSkill { Name = "Skill 1", Description = "Desc Skill 1" }
),
[Test]
public void TestGET()
Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin> {
new EFSkin
{
//Arrange
Name = "Delete Skin",
Description = "Delete skin description",
Icon = "delete_skin_icon.jpg",
Price = 975,
NameChampion = "ChampionToDelete",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "delete_skin_splash.jpg" }
}
}),
//Act
var championResult = ChampionController.get();
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampionToDelete" }
};
//Assert
var objectResult = championResult as OkObjectResult;
//vérifie que cest un ok 200 et un objet
Assert.IsNotNull(objectResult);
context.Add(champion);
await context.SaveChangesAsync();
var champions = objectResult?.Value as IEnumerable<ChampionDto>;
Assert.IsNotNull(champions);
// Act
context.Remove(champion);
await context.SaveChangesAsync();
Assert.AreEqual(champions.Count(), (await StubData.championsMgr.getItems(0, 5)).Count());
// Assert
var deletedChampion = await context.Champions.FirstOrDefaultAsync(c => c.Name == "ChampionToDelete");
Assert.Null(deletedChampion);
}
}
[Test]
public void TestPOST()
[Fact]
public async Task GetChampion_Test()
{
//Arrange
var championDto = new ChampionDto()
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource =:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new SQLiteContext(options))
{
Name = "Darius"
};
await context.Database.EnsureCreatedAsync(); // pour créer la base si elle n'existe pas déjà
//Act
var championResult = await ChampionController.post(championDto);
// Arrange
var championToAdd = new EFChampion
{
Name = "ChampionToGet",
Bio = "Bio Champion to get",
Icon = "get_icon.jpg",
Class = ChampionClass.Marksman,
//Assert
var objectResult = championResult as CreatedAtActionResult;
Assert.IsNotNull(objectResult);
Characteristics = new List<EFCharacteristics> {
new EFCharacteristics { Name = "Attack Damage", Value = 80 },
new EFCharacteristics { Name = "Armor", Value = 32 },
new EFCharacteristics { Name = "Magic Resist", Value = 24 }
},
var champions = objectResult?.Value as ChampionDto;
Assert.IsNotNull(champions);
Skills = ImmutableHashSet.Create<EFSkill>(
new EFSkill { Name = "Skill 1", Description = "Desc Skill 1" },
new EFSkill { Name = "Skill 2", Description = "Desc Skill 2" }
),
Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin> {
new EFSkin
{
Name = "Get Skin",
Description = "Get skin description",
Icon = "get_skin_icon.jpg",
Price = 520,
NameChampion = "ChampionToGet",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "get_skin_splash.jpg" }
}
}),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampionToGet" }
};
context.Add(championToAdd);
await context.SaveChangesAsync();
// Act
var championToGet = await context.Champions.Include(c => c.Characteristics)
.Include(c => c.Skills).Include(c => c.Skins).FirstOrDefaultAsync(c => c.Name == "ChampionToGet");
// Assert
Assert.NotNull(championToGet);
Assert.Equal("Bio Champion to get", championToGet.Bio);
Assert.Equal("get_icon.jpg", championToGet.Icon);
Assert.NotNull(championToGet.Characteristics);
Assert.Equal(3, championToGet.Characteristics.Count);
Assert.Equal(ChampionClass.Marksman, championToGet.Class);
Assert.Single(championToGet.Skins);
if (championToGet.Skins.Any())
{
Assert.Equal("Get Skin", championToGet.Skins[0].Name);
}
Assert.Equal(2, championToGet.Skills.Count);
Assert.Contains(championToGet.Skills, s => s.Name == "Skill 1");
Assert.Contains(championToGet.Skills, s => s.Name == "Skill 2");
Assert.Equal("CheminDeMonChampionToGet", championToGet.Image.Base64);
}
}
} */
}
}

@ -1,5 +1,11 @@
using System;
using EFlib;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Model;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -8,5 +14,241 @@ namespace TestEF
{
public class UnitTestSkin
{
[Fact]
public async Task AddSkin_Test()
{
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new SQLiteContext(options))
{
await context.Database.EnsureCreatedAsync();
// Arrange
var champion = new EFChampion
{
Name = "Champion 1",
Bio = "Bio Champion 1",
Icon = "aatrox_icon.jpg",
Class = ChampionClass.Assassin,
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
var skin = new EFSkin
{
Name = "Mecha Kingdoms Aatrox",
Description = "In a distant cyberfuture, the champion Aatrox becomes a symbol of ultimate power and glory...",
Icon = "aatrox_mecha_icon.jpg",
Price = 1350,
NameChampion = "Champion 1",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
};
var skinsList = new List<EFSkin>();
skinsList.Add(skin);
champion.Skins = new ReadOnlyCollection<EFSkin>(skinsList);
context.Add(champion);
await context.SaveChangesAsync();
// Act
var championFromDb = await context.Champions.Include(c => c.Skins).FirstOrDefaultAsync(c => c.Name == "Champion 1");
// Assert
Assert.NotNull(championFromDb);
Assert.Single(championFromDb.Skins);
if (championFromDb.Skins.Any())
{
Assert.Equal("Mecha Kingdoms Aatrox", championFromDb.Skins[0].Name);
}
}
}
[Fact]
public async Task UpdateSkin_Test()
{
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new SQLiteContext(options))
{
await context.Database.EnsureCreatedAsync(); // pour créer la base si elle n'existe pas déjà
// Arrange
var champion = new EFChampion
{
Name = "Champion 1",
Bio = "Bio Champion 1",
Icon = "aatrox_icon.jpg",
Class = ChampionClass.Assassin,
Characteristics = new List<EFCharacteristics>
{
new EFCharacteristics { Name = "Attack Damage", Value = 60 },
new EFCharacteristics { Name = "Armor", Value = 38 },
new EFCharacteristics { Name = "Magic Resist", Value = 32 }
},
Skills = ImmutableHashSet.Create<EFSkill>(
new EFSkill { Name = "Skill 1", Description = "Desc Skill 1" },
new EFSkill { Name = "Skill 2", Description = "Desc Skill 2" }
),
Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin>
{
new EFSkin
{
Name = "Mecha Kingdoms Aatrox",
Description = "In a distant cyberfuture, the champion Aatrox becomes a symbol of ultimate power and glory...",
Icon = "aatrox_mecha_icon.jpg",
Price = 1350,
NameChampion = "Aatrox",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
}
}),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
context.Add(champion);
await context.SaveChangesAsync();
var skinToUpdate = champion.Skins.FirstOrDefault();
skinToUpdate.Description = "New description"; // update the description
// Act
context.Update(skinToUpdate);
await context.SaveChangesAsync();
// Assert
var updatedSkin = await context.Skins.FirstOrDefaultAsync(s => s.Name == skinToUpdate.Name);
Assert.NotNull(updatedSkin);
Assert.Equal("New description", updatedSkin.Description);
}
}
// Pour le test de delete on est obligé de supprimer le champion à cause de la clef étrangère, car on part du principe qu'un skin ne peut exister s'il y a un champion
[Fact]
public async Task DeleteSkin_Test()
{
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new SQLiteContext(options))
{
await context.Database.EnsureCreatedAsync();
// Arrange
EFChampion champion = new EFChampion
{
Name = "Champion 1",
Bio = "Bio Champion 1",
Icon = "aatrox_icon.jpg",
Class = ChampionClass.Assassin,
Characteristics = new List<EFCharacteristics> {
new EFCharacteristics { Name = "Attack Damage", Value = 60 },
new EFCharacteristics { Name = "Armor", Value = 38 },
new EFCharacteristics { Name = "Magic Resist", Value = 32 }
},
Skills = ImmutableHashSet.Create<EFSkill>(
new EFSkill { Name = "Skill 1", Description = "Desc Skill 1" },
new EFSkill { Name = "Skill 2", Description = "Desc Skill 2" }
),
Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin> {
new EFSkin
{
Name = "Mecha Kingdoms Aatrox",
Description = "In a distant cyberfuture, the champion Aatrox becomes a symbol of ultimate power and glory...",
Icon = "aatrox_mecha_icon.jpg",
Price = 1350,
NameChampion = "Aatrox",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
} }),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
context.Add(champion);
await context.SaveChangesAsync();
// Act
context.Champions.Remove(champion);
await context.SaveChangesAsync();
// Assert
var deletedChampion = await context.Champions.FirstOrDefaultAsync(c => c.Name == "Champion 1");
Assert.Null(deletedChampion);
var deletedSkin = await context.Skins.FirstOrDefaultAsync(s => s.Name == "Mecha Kingdoms Aatrox");
Assert.Null(deletedSkin);
}
}
[Fact]
public async Task GetSkin_Test()
{
// connection must be opened to use In-memory database
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
var options = new DbContextOptionsBuilder<SQLiteContext>().UseSqlite(connection).Options;
using (var context = new SQLiteContext(options))
{
await context.Database.EnsureCreatedAsync();
// Arrange
var champion = new EFChampion
{
Name = "Champion 1",
Bio = "Bio Champion 1",
Icon = "aatrox_icon.jpg",
Class = ChampionClass.Assassin,
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "CheminDeMonChampion" }
};
var skin = new EFSkin
{
Name = "Mecha Kingdoms Aatrox",
Description = "In a distant cyberfuture, the champion Aatrox becomes a symbol of ultimate power and glory...",
Icon = "aatrox_mecha_icon.jpg",
Price = 1350,
NameChampion = "Champion 1",
ImageId = Guid.NewGuid(),
Image = new EFLargeImage { Id = Guid.NewGuid(), Base64 = "aatrox_mecha_splash.jpg" }
};
champion.Skins = new ReadOnlyCollection<EFSkin>(new List<EFSkin> { skin });
context.Add(champion);
await context.SaveChangesAsync();
// Act
var skinFromDb = await context.Skins.Include(s => s.Champion).FirstOrDefaultAsync(s => s.Name == "Mecha Kingdoms Aatrox");
// Assert
Assert.NotNull(skinFromDb);
Assert.Equal("Mecha Kingdoms Aatrox", skinFromDb.Name);
Assert.Equal("Champion 1", skinFromDb.Champion.Name);
}
}
}
}

@ -21,4 +21,9 @@
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\EntityFramework\EFlib\EFlib.csproj" />
<ProjectReference Include="..\..\EntityFramework\EFManager\EFManager.csproj" />
</ItemGroup>
</Project>

@ -1,11 +1,12 @@
using EFlib;
using EFManager;
using Model;
using static EFManager.ManagerData;
namespace TestManagerEF
{
public class UnitTestManagerChampion
{
[Fact]
public void Test1()
{
}
}
}

@ -1,4 +1,6 @@
using System;
using EFManager;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -6,7 +8,10 @@ using System.Threading.Tasks;
namespace TestManagerEF
{
public class UnitTestManagerSkin
{
}
}
Loading…
Cancel
Save