diff --git a/src/Predictalytics.Application.Tests/Services/MarketRepositoryTests.cs b/src/Predictalytics.Application.Tests/Services/MarketRepositoryTests.cs
index 5fc434c..783ec2a 100644
--- a/src/Predictalytics.Application.Tests/Services/MarketRepositoryTests.cs
+++ b/src/Predictalytics.Application.Tests/Services/MarketRepositoryTests.cs
@@ -90,4 +90,67 @@ public class MarketRepositoryTests
Assert.Equal("Elections", market.Subcategory);
}
}
+
+ ///
+ /// Regression (2026-08-04): only Slug and Title were capped on the event upsert,
+ /// so a Polymarket event with an over-long Description made MySQL reject the whole
+ /// batch with "Data too long for column 'Description'" — aborting the entire market
+ /// sync, not just that one event. Every length-constrained Event field must be capped.
+ ///
+ /// SQLite ignores varchar limits, so asserting "no exception" would prove nothing:
+ /// the assertions check the stored lengths instead.
+ ///
+ [Theory]
+ [InlineData(true)] // event already exists → update branch
+ [InlineData(false)] // new event → insert branch
+ public async Task AddOrUpdateEventsAsync_CapsAllLengthConstrainedFields(bool eventAlreadyExists)
+ {
+ using var connection = new SqliteConnection("DataSource=:memory:");
+ connection.Open();
+ var options = new DbContextOptionsBuilder()
+ .UseSqlite(connection)
+ .Options;
+
+ using (var setup = new AppDbContext(options))
+ {
+ setup.Database.EnsureCreated();
+ if (eventAlreadyExists)
+ {
+ setup.Set().Add(new Event
+ {
+ Id = 1, Platform = PlatformType.Polymarket, PlatformEventId = 99L,
+ Slug = "existing", Title = "Existing", Description = "short"
+ });
+ setup.SaveChanges();
+ }
+ }
+
+ using (var ctx = new AppDbContext(options))
+ {
+ var repo = new MarketRepository(ctx);
+ await repo.AddOrUpdateEventsAsync(new[]
+ {
+ new Event
+ {
+ Platform = PlatformType.Polymarket,
+ PlatformEventId = 99L,
+ Slug = new string('s', 900),
+ Title = new string('t', 1500),
+ Description = new string('d', 6000),
+ ImageUrl = "https://x/" + new string('i', 2000),
+ Tags = new string('g', 2000)
+ }
+ });
+ }
+
+ using (var assertCtx = new AppDbContext(options))
+ {
+ var stored = await assertCtx.Set().SingleAsync(e => e.PlatformEventId == 99L);
+ Assert.Equal(512, stored.Slug.Length);
+ Assert.Equal(1024, stored.Title.Length);
+ Assert.Equal(4096, stored.Description!.Length);
+ Assert.Equal(1024, stored.ImageUrl!.Length);
+ Assert.Equal(1024, stored.Tags.Length);
+ }
+ }
}
diff --git a/src/Predictalytics.Infrastructure/Data/Repositories/MarketRepository.cs b/src/Predictalytics.Infrastructure/Data/Repositories/MarketRepository.cs
index bd46b22..e130545 100644
--- a/src/Predictalytics.Infrastructure/Data/Repositories/MarketRepository.cs
+++ b/src/Predictalytics.Infrastructure/Data/Repositories/MarketRepository.cs
@@ -136,8 +136,7 @@ public class MarketRepository : IMarketRepository
foreach (var ev in currentBatch)
{
- if (ev.Slug != null && ev.Slug.Length > 512) ev.Slug = ev.Slug[..512];
- if (ev.Title != null && ev.Title.Length > 1024) ev.Title = ev.Title[..1024];
+ TruncateEventStrings(ev);
if (existingEventsMap.TryGetValue(ev.PlatformEventId, out var existing))
{
@@ -231,6 +230,22 @@ public class MarketRepository : IMarketRepository
}
}
+ ///
+ /// Caps every length-constrained Event string to its column width.
+ /// Must cover ALL such fields: MySQL rejects the whole batch with
+ /// "Data too long for column ..." if a single one overflows, so one long
+ /// description aborts the entire market sync — not just that one event.
+ /// Keep in sync with the HasMaxLength calls in AppDbContext.
+ ///
+ private void TruncateEventStrings(Event ev)
+ {
+ ev.Slug = StringHelper.Truncate(ev.Slug, 512) ?? "";
+ ev.Title = StringHelper.Truncate(ev.Title, 1024) ?? "";
+ ev.Description = StringHelper.Truncate(ev.Description, 4096);
+ ev.ImageUrl = StringHelper.Truncate(ev.ImageUrl, 1024);
+ ev.Tags = StringHelper.Truncate(ev.Tags, 1024) ?? "";
+ }
+
private void TruncateMarketStrings(Market market)
{
market.Question = StringHelper.Truncate(market.Question, 1024) ?? "";