Skip to content

Commit a93de06

Browse files
committed
Add migration for HeritageCity table
- Created the `HeritageCity` table with columns `Name`, `Description`, `CityId`, `DepartmentId`, and `Image`. - Established foreign key relationships with `City` and `Department`, using `onDelete: SetNull`. - Added indices for `CityId` and `DepartmentId` in the new table.
1 parent d367efb commit a93de06

16 files changed

+1681
-7
lines changed

CHANGELOG.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning].
99

1010
- /
1111

12+
## [1.0.9] - 2026-01-26
13+
14+
### Added
15+
16+
- New HeritageCity model and endpoints for Colombia's heritage cities.
17+
- HeritageCity table with fields: Id, Name, Description, CityId, DepartmentId, and Image.
18+
- Complete REST API endpoints for HeritageCity including list, get by id, get by name, search, and paginated list.
19+
- SQL seed script for HeritageCity data.
20+
1221
## [1.0.8] - 2026-01-25
1322

1423
### Added
@@ -75,12 +84,13 @@ and this project adheres to [Semantic Versioning].
7584
[semantic versioning]: https://semver.org/spec/v2.0.0.html
7685

7786
<!-- Versions -->
78-
[unreleased]: https://github.com/Author/Repository/compare/v1.0.8...HEAD
87+
[unreleased]: https://github.com/Author/Repository/compare/v1.0.9...HEAD
88+
[1.0.9]: https://github.com/Author/Repository/compare/v1.0.8...v1.0.9
7989
[1.0.8]: https://github.com/Author/Repository/compare/v1.0.7...v1.0.8
8090
[1.0.7]: https://github.com/Author/Repository/compare/v1.0.6...v1.0.7
8191
[1.0.6]: https://github.com/Author/Repository/compare/v1.0.5...v1.0.6
8292
[1.0.5]: https://github.com/Author/Repository/compare/v1.0.4...v1.0.5
8393
[1.0.4]: https://github.com/Author/Repository/compare/v1.0.3...v1.0.4
8494
[1.0.3]: https://github.com/Author/Repository/compare/v1.0.2...v1.0.3
8595
[1.0.2]: https://github.com/Author/Repository/compare/v1.0.1...v1.0.2
86-
[1.0.1]: https://github.com/Author/Repository/releases/tag/v1.0.1
96+
[1.0.1]: https://github.com/Author/Repository/releases/tag/v1.0.1

api.Tests/ApiRoutesTests/CustomWebApplicationFactory.cs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,78 @@ private void SeedDatabase(DBContext dbContext)
530530
});
531531
}
532532

533+
if(!dbContext.IntangibleHeritages.Any())
534+
{
535+
dbContext.Add(new IntangibleHeritage
536+
{
537+
Id = 1,
538+
Name = "Carnaval de Barranquilla",
539+
DepartmentId = deparment1.Id,
540+
Department = deparment1,
541+
Scope = "Nacional",
542+
InclusionYear = 2003,
543+
});
544+
545+
dbContext.Add(new IntangibleHeritage
546+
{
547+
Id = 2,
548+
Name = "Fiesta de las Flores",
549+
DepartmentId = deparment1.Id,
550+
Department = deparment1,
551+
Scope = "Regional",
552+
InclusionYear = 2010,
553+
});
554+
555+
dbContext.Add(new IntangibleHeritage
556+
{
557+
Id = 3,
558+
Name = "Semana Santa",
559+
DepartmentId = deparment2.Id,
560+
Department = deparment2,
561+
Scope = "Nacional",
562+
InclusionYear = 2012,
563+
});
564+
}
565+
566+
if (!dbContext.HeritageCities.Any())
567+
{
568+
dbContext.Add(new HeritageCity
569+
{
570+
Id = 1,
571+
Name = "Cartagena",
572+
Description = "Historic walled city with colonial architecture.",
573+
CityId = city1.Id,
574+
City = city1,
575+
DepartmentId = deparment1.Id,
576+
Department = deparment1,
577+
Image = "https://example.com/cartagena.jpg",
578+
});
579+
580+
dbContext.Add(new HeritageCity
581+
{
582+
Id = 2,
583+
Name = "Popayan",
584+
Description = "Colonial city known for its white architecture.",
585+
CityId = city1.Id,
586+
City = city1,
587+
DepartmentId = deparment1.Id,
588+
Department = deparment1,
589+
Image = "https://example.com/popayan.jpg",
590+
});
591+
592+
dbContext.Add(new HeritageCity
593+
{
594+
Id = 3,
595+
Name = "Santa Cruz de Mompox",
596+
Description = "River town with preserved colonial heritage.",
597+
CityId = city1.Id,
598+
City = city1,
599+
DepartmentId = deparment1.Id,
600+
Department = deparment1,
601+
Image = "https://example.com/mompox.jpg",
602+
});
603+
}
604+
533605
dbContext.SaveChanges();
534606
}
535607

@@ -548,4 +620,4 @@ public void ResetDatabase()
548620
SeedDatabase(dbContext);
549621
}
550622
}
551-
}
623+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Net.Http.Json;
2+
using api.Models;
3+
using api.Utils;
4+
5+
namespace api.Tests.ApiRoutesTests;
6+
7+
public class HeritageCityApiIntegrationTests : IClassFixture<CustomWebApplicationFactory>
8+
{
9+
private readonly HttpClient _client;
10+
11+
public HeritageCityApiIntegrationTests(CustomWebApplicationFactory factory)
12+
{
13+
factory.ResetDatabase();
14+
_client = factory.CreateClient();
15+
}
16+
17+
[Fact]
18+
public async Task GetHeritageCities_ReturnsOkWithExpectedData()
19+
{
20+
var response = await _client.GetAsync("/api/v1/HeritageCity");
21+
22+
response.EnsureSuccessStatusCode();
23+
24+
var result = await response.Content.ReadFromJsonAsync<List<HeritageCity>>();
25+
26+
Assert.NotNull(result);
27+
Assert.NotEmpty(result);
28+
Assert.Equal(3, result.Count);
29+
}
30+
31+
[Fact]
32+
public async Task GetHeritageCityById_ReturnsOkWithData()
33+
{
34+
int id = 1;
35+
var response = await _client.GetAsync($"/api/v1/HeritageCity/{id}");
36+
37+
response.EnsureSuccessStatusCode();
38+
39+
var result = await response.Content.ReadFromJsonAsync<HeritageCity>();
40+
41+
Assert.NotNull(result);
42+
Assert.Equal(id, result.Id);
43+
Assert.Equal("Cartagena", result.Name);
44+
}
45+
46+
[Fact]
47+
public async Task GetHeritageCityById_ReturnsBadRequest()
48+
{
49+
int id = 0;
50+
var response = await _client.GetAsync($"/api/v1/HeritageCity/{id}");
51+
52+
Assert.False(response.IsSuccessStatusCode);
53+
Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
54+
}
55+
56+
[Fact]
57+
public async Task GetHeritageCityByName_ReturnsOkWithExpectedData()
58+
{
59+
string name = "Cartagena";
60+
var response = await _client.GetAsync($"/api/v1/HeritageCity/name/{name}");
61+
62+
response.EnsureSuccessStatusCode();
63+
64+
var result = await response.Content.ReadFromJsonAsync<List<HeritageCity>>();
65+
66+
Assert.NotNull(result);
67+
Assert.NotEmpty(result);
68+
Assert.Single(result);
69+
Assert.Equal(name, result[0].Name);
70+
}
71+
72+
[Fact]
73+
public async Task SearchHeritageCities_ReturnsOkWithFilteredResults()
74+
{
75+
string keyword = "mompox";
76+
var response = await _client.GetAsync($"/api/v1/HeritageCity/search/{keyword}");
77+
78+
response.EnsureSuccessStatusCode();
79+
80+
var result = await response.Content.ReadFromJsonAsync<List<HeritageCity>>();
81+
82+
Assert.NotNull(result);
83+
Assert.NotEmpty(result);
84+
Assert.Single(result);
85+
Assert.Contains(keyword, result[0].Name, StringComparison.OrdinalIgnoreCase);
86+
}
87+
88+
[Fact]
89+
public async Task GetPagedHeritageCities_ReturnsOkWithPagedData()
90+
{
91+
int page = 1;
92+
int pageSize = 2;
93+
94+
var response = await _client.GetAsync($"/api/v1/HeritageCity/pagedList?page={page}&pageSize={pageSize}");
95+
96+
response.EnsureSuccessStatusCode();
97+
98+
var result = await response.Content.ReadFromJsonAsync<PaginationResponseModel<HeritageCity>>();
99+
100+
Assert.NotNull(result);
101+
Assert.Equal(pageSize, result.PageSize);
102+
Assert.Equal(page, result.Page);
103+
Assert.Equal(pageSize, result.Data.Count);
104+
Assert.Equal(3, result.TotalRecords);
105+
}
106+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
using System.Net.Http.Json;
2+
using api.Models;
3+
using api.Utils;
4+
5+
namespace api.Tests.ApiRoutesTests;
6+
7+
public class IntangibleHeritageApiIntegrationTests : IClassFixture<CustomWebApplicationFactory>
8+
{
9+
private readonly HttpClient _client;
10+
11+
public IntangibleHeritageApiIntegrationTests(CustomWebApplicationFactory factory)
12+
{
13+
factory.ResetDatabase();
14+
_client = factory.CreateClient();
15+
}
16+
17+
[Fact]
18+
public async Task GetIntangibleHeritages_ReturnsOkWithExpectedData()
19+
{
20+
var response = await _client.GetAsync("/api/v1/IntangibleHeritage");
21+
22+
response.EnsureSuccessStatusCode();
23+
24+
var result = await response.Content.ReadFromJsonAsync<List<IntangibleHeritage>>();
25+
26+
Assert.NotNull(result);
27+
Assert.NotEmpty(result);
28+
Assert.Equal(3, result.Count);
29+
}
30+
31+
[Fact]
32+
public async Task GetIntangibleHeritageById_ReturnsOkWithData()
33+
{
34+
int id = 1;
35+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/{id}");
36+
37+
response.EnsureSuccessStatusCode();
38+
39+
var result = await response.Content.ReadFromJsonAsync<IntangibleHeritage>();
40+
41+
Assert.NotNull(result);
42+
Assert.Equal(id, result.Id);
43+
Assert.Equal("Carnaval de Barranquilla", result.Name);
44+
}
45+
46+
[Fact]
47+
public async Task GetIntangibleHeritageById_ReturnsBadRequest()
48+
{
49+
int id = 0;
50+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/{id}");
51+
52+
Assert.False(response.IsSuccessStatusCode);
53+
Assert.Equal(System.Net.HttpStatusCode.BadRequest, response.StatusCode);
54+
}
55+
56+
[Fact]
57+
public async Task GetIntangibleHeritageByDepartment_ReturnsOkWithFilteredData()
58+
{
59+
int departmentId = 1;
60+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/{departmentId}/department");
61+
62+
response.EnsureSuccessStatusCode();
63+
64+
var result = await response.Content.ReadFromJsonAsync<List<IntangibleHeritage>>();
65+
66+
Assert.NotNull(result);
67+
Assert.NotEmpty(result);
68+
Assert.All(result, item => Assert.Equal(departmentId, item.DepartmentId));
69+
Assert.Equal(2, result.Count);
70+
}
71+
72+
[Fact]
73+
public async Task GetIntangibleHeritageByName_ReturnsOkWithExpectedData()
74+
{
75+
string name = "Carnaval de Barranquilla";
76+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/name/{name}");
77+
78+
response.EnsureSuccessStatusCode();
79+
80+
var result = await response.Content.ReadFromJsonAsync<List<IntangibleHeritage>>();
81+
82+
Assert.NotNull(result);
83+
Assert.NotEmpty(result);
84+
Assert.Single(result);
85+
Assert.Equal(name, result[0].Name);
86+
}
87+
88+
[Fact]
89+
public async Task SearchIntangibleHeritages_ReturnsOkWithFilteredResults()
90+
{
91+
string keyword = "carn";
92+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/search/{keyword}");
93+
94+
response.EnsureSuccessStatusCode();
95+
96+
var result = await response.Content.ReadFromJsonAsync<List<IntangibleHeritage>>();
97+
98+
Assert.NotNull(result);
99+
Assert.NotEmpty(result);
100+
Assert.Single(result);
101+
Assert.Contains(keyword, result[0].Name, StringComparison.OrdinalIgnoreCase);
102+
}
103+
104+
[Fact]
105+
public async Task GetPagedIntangibleHeritages_ReturnsOkWithPagedData()
106+
{
107+
int page = 1;
108+
int pageSize = 2;
109+
110+
var response = await _client.GetAsync($"/api/v1/IntangibleHeritage/pagedList?page={page}&pageSize={pageSize}");
111+
112+
response.EnsureSuccessStatusCode();
113+
114+
var result = await response.Content.ReadFromJsonAsync<PaginationResponseModel<IntangibleHeritage>>();
115+
116+
Assert.NotNull(result);
117+
Assert.Equal(pageSize, result.PageSize);
118+
Assert.Equal(page, result.Page);
119+
Assert.Equal(pageSize, result.Data.Count);
120+
Assert.Equal(3, result.TotalRecords);
121+
}
122+
}

api/Const/Version.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ namespace api.Const;
22

33
public static class VersionInfo
44
{
5-
public const string CurrentVersion = "1.0.8";
5+
public const string CurrentVersion = "1.0.9";
66
}

api/DBContext.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public class DBContext : DbContext
2424
public DbSet<Radio> Radios { get; set; }
2525
public DbSet<Holiday> Holidays {get; set; }
2626
public DbSet<IntangibleHeritage> IntangibleHeritages {get; set; }
27+
public DbSet<HeritageCity> HeritageCities { get; set; }
2728

2829
public DBContext(DbContextOptions<DBContext> options) : base(options)
2930
{
@@ -51,6 +52,7 @@ protected override void OnModelCreating(ModelBuilder builder)
5152
builder.ApplyConfiguration(new TypicalDishConfig());
5253
builder.ApplyConfiguration(new TraditionalFairAndFestivalConfig());
5354
builder.ApplyConfiguration(new IntangibleHeritageConfig());
55+
builder.ApplyConfiguration(new HeritageCityConfig());
5456

5557
base.OnModelCreating(builder);
5658
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using api.Models;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Metadata.Builders;
4+
5+
public class HeritageCityConfig : IEntityTypeConfiguration<HeritageCity>
6+
{
7+
public void Configure(EntityTypeBuilder<HeritageCity> heritageCity)
8+
{
9+
heritageCity.ToTable("HeritageCity");
10+
heritageCity.HasKey(t => t.Id);
11+
heritageCity.Property(t => t.Id).ValueGeneratedOnAdd();
12+
heritageCity.Property(t => t.Name).IsRequired().HasMaxLength(200);
13+
heritageCity.Property(t => t.Description).IsRequired().HasMaxLength(2000);
14+
heritageCity.Property(t => t.Image).IsRequired().HasMaxLength(255);
15+
heritageCity.Property(t => t.CityId).IsRequired();
16+
heritageCity.Property(t => t.DepartmentId).IsRequired();
17+
heritageCity.HasOne(t => t.City).WithMany().HasForeignKey(t => t.CityId).OnDelete(DeleteBehavior.SetNull);
18+
heritageCity.HasOne(t => t.Department).WithMany().HasForeignKey(t => t.DepartmentId).OnDelete(DeleteBehavior.SetNull);
19+
}
20+
}

0 commit comments

Comments
 (0)