Skip to content

Commit c6f2068

Browse files
committed
fix: parameterize the run-flag SQL
1 parent ad6a41a commit c6f2068

2 files changed

Lines changed: 87 additions & 15 deletions

File tree

SW.Bitween.Api/Services/RunFlagUpdater.cs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ public async Task<bool> MarkAsRunning(int id)
2121
{
2222
var sqlUpdate = _dbType.ToLower() switch
2323
{
24-
"pgsql" => $@"UPDATE infolink.subscription SET is_running = true
25-
WHERE id = '{id}' and is_running = false
24+
"pgsql" => @"UPDATE infolink.subscription SET is_running = true
25+
WHERE id = {0} and is_running = false
2626
RETURNING is_running",
27-
"mssql" => $@"UPDATE Subscriptions SET IsRunning = 1
27+
"mssql" => @"UPDATE Subscriptions SET IsRunning = 1
2828
OUTPUT INSERTED.IsRunning
29-
WHERE Id = '{id}' and IsRunning = 0",
30-
"mysql" => $@"SELECT IsRunning FROM Subscriptions
31-
WHERE Id = '{id}' and IsRunning = false
29+
WHERE Id = {0} and IsRunning = 0",
30+
"mysql" => @"SELECT IsRunning FROM Subscriptions
31+
WHERE Id = {0} and IsRunning = false
3232
FOR UPDATE;
3333
UPDATE Subscriptions SET IsRunning = true
34-
WHERE Id = '{id}' and IsRunning = false",
34+
WHERE Id = {0} and IsRunning = false",
3535
_ => ""
3636
};
3737

38-
var results = await dbContext.Set<RunningResult>().FromSqlRaw(sqlUpdate).ToListAsync();
38+
var results = await dbContext.Set<RunningResult>().FromSqlRaw(sqlUpdate, id).ToListAsync();
3939

4040
var result = results.SingleOrDefault();
4141
// result is null when is running is true
@@ -46,17 +46,17 @@ public async Task MarkAsIdle(int id)
4646
{
4747
var sqlUpdate = _dbType.ToLower() switch
4848
{
49-
"pgsql" => $@"UPDATE infolink.subscription SET is_running = false
50-
WHERE id = '{id}'",
51-
"mssql" => $@"UPDATE Subscriptions SET IsRunning = 0
52-
WHERE Id = '{id}'",
53-
"mysql" => $@"UPDATE Subscriptions SET IsRunning = false
54-
WHERE Id = '{id}'",
49+
"pgsql" => @"UPDATE infolink.subscription SET is_running = false
50+
WHERE id = {0}",
51+
"mssql" => @"UPDATE Subscriptions SET IsRunning = 0
52+
WHERE Id = {0}",
53+
"mysql" => @"UPDATE Subscriptions SET IsRunning = false
54+
WHERE Id = {0}",
5555
_ => ""
5656
};
5757
;
5858

59-
await dbContext.Database.ExecuteSqlRawAsync(sqlUpdate);
59+
await dbContext.Database.ExecuteSqlRawAsync(sqlUpdate, id);
6060
}
6161

6262
public class RunningResult
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using SW.Bitween.Domain;
4+
using SW.Bitween.IntegrationTests.Fixtures;
5+
using SW.Bitween.Model;
6+
using Xunit;
7+
8+
namespace SW.Bitween.IntegrationTests.Tests;
9+
10+
// The run flag is written with raw (parameterized) SQL that differs per database
11+
// provider, so it needs a real round trip to prove the statement and its parameter
12+
// binding are correct. There was no coverage here before.
13+
[Collection("Bitween")]
14+
public class RunFlagUpdaterTests
15+
{
16+
private readonly BitweenFixture _fixture;
17+
18+
public RunFlagUpdaterTests(BitweenFixture fixture)
19+
{
20+
_fixture = fixture;
21+
}
22+
23+
[Fact]
24+
public async Task Run_flag_claims_once_then_blocks_until_idle()
25+
{
26+
await using var scope = _fixture.CreateScope();
27+
var db = scope.ServiceProvider.GetRequiredService<BitweenDbContext>();
28+
var runFlag = scope.ServiceProvider.GetRequiredService<RunFlagUpdater>();
29+
30+
var document = new Document(6101, "Run Flag Test Doc");
31+
db.Set<Document>().Add(document);
32+
var subscription = new Subscription("Run Flag Test", document.Id);
33+
subscription.Inactive = false;
34+
db.Set<Subscription>().Add(subscription);
35+
await db.SaveChangesAsync();
36+
37+
Assert.True(await runFlag.MarkAsRunning(subscription.Id)); // first claim wins
38+
Assert.False(await runFlag.MarkAsRunning(subscription.Id)); // already running
39+
40+
await runFlag.MarkAsIdle(subscription.Id);
41+
42+
Assert.True(await runFlag.MarkAsRunning(subscription.Id)); // claimable again
43+
await runFlag.MarkAsIdle(subscription.Id);
44+
}
45+
46+
// Guards the parameter binding: a broken placeholder would either match no rows
47+
// or every row, and both would show up here.
48+
[Fact]
49+
public async Task Run_flag_only_affects_the_requested_subscription()
50+
{
51+
await using var scope = _fixture.CreateScope();
52+
var db = scope.ServiceProvider.GetRequiredService<BitweenDbContext>();
53+
var runFlag = scope.ServiceProvider.GetRequiredService<RunFlagUpdater>();
54+
55+
var document = new Document(6102, "Run Flag Isolation Doc");
56+
db.Set<Document>().Add(document);
57+
var a = new Subscription("Run Flag A", document.Id);
58+
a.Inactive = false;
59+
var b = new Subscription("Run Flag B", document.Id);
60+
b.Inactive = false;
61+
db.Set<Subscription>().AddRange(a, b);
62+
await db.SaveChangesAsync();
63+
64+
Assert.True(await runFlag.MarkAsRunning(a.Id));
65+
Assert.True(await runFlag.MarkAsRunning(b.Id)); // b untouched by a's update
66+
67+
await runFlag.MarkAsIdle(a.Id);
68+
Assert.False(await runFlag.MarkAsRunning(b.Id)); // b still running, a's idle did not clear it
69+
70+
await runFlag.MarkAsIdle(b.Id);
71+
}
72+
}

0 commit comments

Comments
 (0)