Skip to content

Commit ba15fb7

Browse files
test: Add tests for retention policies
1 parent 590b124 commit ba15fb7

12 files changed

Lines changed: 6318 additions & 49 deletions

src/Eftdb/Generators/RetentionPolicyOperationGenerator.cs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,6 @@ public List<string> Generate(AddRetentionPolicyOperation operation)
2525
BuildAddRetentionPolicySql(operation.TableName, operation.Schema, operation.DropAfter, operation.DropCreatedBefore, operation.InitialStart)
2626
];
2727

28-
// alter_job fails for drop_created_before retention policies.
29-
// TimescaleDB's policy_retention_check expects drop_after in the job config JSONB
30-
// but finds drop_created_before instead. Workaround: avoid alter_job for
31-
// drop_created_before policies, or recreate the policy entirely.
32-
// TODO: Remove this when a fix has been applied to TimescaleDB.
33-
if (string.IsNullOrEmpty(operation.DropAfter))
34-
{
35-
return statements;
36-
}
37-
3828
List<string> alterJobClauses = BuildAlterJobClauses(operation);
3929
if (alterJobClauses.Count != 0)
4030
{
@@ -106,6 +96,16 @@ private static List<string> BuildAlterJobClauses(AddRetentionPolicyOperation ope
10696
{
10797
List<string> clauses = [];
10898

99+
// alter_job fails for drop_created_before retention policies.
100+
// TimescaleDB's policy_retention_check expects drop_after in the job config JSONB
101+
// but finds drop_created_before instead. Workaround: avoid alter_job for
102+
// drop_created_before policies, or recreate the policy entirely.
103+
// TODO: Remove this when a fix has been applied to TimescaleDB.
104+
if (!string.IsNullOrEmpty(operation.DropCreatedBefore))
105+
{
106+
return clauses;
107+
}
108+
109109
if (!string.IsNullOrWhiteSpace(operation.ScheduleInterval))
110110
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");
111111

@@ -125,6 +125,16 @@ private static List<string> BuildAlterJobClauses(AlterRetentionPolicyOperation o
125125
{
126126
List<string> clauses = [];
127127

128+
// alter_job fails for drop_created_before retention policies.
129+
// TimescaleDB's policy_retention_check expects drop_after in the job config JSONB
130+
// but finds drop_created_before instead. Workaround: avoid alter_job for
131+
// drop_created_before policies, or recreate the policy entirely.
132+
// TODO: Remove this when a fix has been applied to TimescaleDB.
133+
if (!string.IsNullOrEmpty(operation.DropCreatedBefore))
134+
{
135+
return clauses;
136+
}
137+
128138
if (!string.IsNullOrWhiteSpace(operation.ScheduleInterval) && operation.ScheduleInterval != operation.OldScheduleInterval)
129139
clauses.Add($"schedule_interval => INTERVAL '{operation.ScheduleInterval}'");
130140

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
using CmdScale.EntityFrameworkCore.TimescaleDB.Configuration.RetentionPolicy;
2+
3+
namespace CmdScale.EntityFrameworkCore.TimescaleDB.Tests.Configuration;
4+
5+
/// <summary>
6+
/// Tests that verify RetentionPolicyAttribute constructor validation, mutual exclusivity, and default values.
7+
/// </summary>
8+
public class RetentionPolicyAttributeTests
9+
{
10+
#region Constructor1 Validation Tests (string dropAfter)
11+
12+
[Fact]
13+
public void Constructor1_With_Null_DropAfter_ThrowsArgumentException()
14+
{
15+
// Arrange & Act & Assert
16+
ArgumentException ex = Assert.Throws<ArgumentException>(() => new RetentionPolicyAttribute(null!));
17+
Assert.Contains("DropAfter must be provided", ex.Message);
18+
Assert.Equal("dropAfter", ex.ParamName);
19+
}
20+
21+
[Fact]
22+
public void Constructor1_With_Empty_DropAfter_ThrowsArgumentException()
23+
{
24+
// Arrange & Act & Assert
25+
ArgumentException ex = Assert.Throws<ArgumentException>(() => new RetentionPolicyAttribute(""));
26+
Assert.Contains("DropAfter must be provided", ex.Message);
27+
Assert.Equal("dropAfter", ex.ParamName);
28+
}
29+
30+
[Fact]
31+
public void Constructor1_With_Whitespace_DropAfter_ThrowsArgumentException()
32+
{
33+
// Arrange & Act & Assert
34+
ArgumentException ex = Assert.Throws<ArgumentException>(() => new RetentionPolicyAttribute(" "));
35+
Assert.Contains("DropAfter must be provided", ex.Message);
36+
Assert.Equal("dropAfter", ex.ParamName);
37+
}
38+
39+
[Fact]
40+
public void Constructor1_With_Tabs_DropAfter_ThrowsArgumentException()
41+
{
42+
// Arrange & Act & Assert
43+
ArgumentException ex = Assert.Throws<ArgumentException>(() => new RetentionPolicyAttribute("\t\t"));
44+
Assert.Contains("DropAfter must be provided", ex.Message);
45+
}
46+
47+
[Fact]
48+
public void Constructor1_With_Valid_DropAfter_SetsDropAfterCorrectly()
49+
{
50+
// Arrange & Act
51+
RetentionPolicyAttribute attr = new("7 days");
52+
53+
// Assert
54+
Assert.Equal("7 days", attr.DropAfter);
55+
}
56+
57+
#endregion
58+
59+
#region Constructor2 Mutual Exclusivity Tests (string? dropAfter, string? dropCreatedBefore)
60+
61+
[Fact]
62+
public void Constructor2_With_DropAfterOnly_SetsDropAfterAndDropCreatedBeforeIsNull()
63+
{
64+
// Arrange & Act
65+
RetentionPolicyAttribute attr = new(dropAfter: "7 days");
66+
67+
// Assert
68+
Assert.Equal("7 days", attr.DropAfter);
69+
Assert.Null(attr.DropCreatedBefore);
70+
}
71+
72+
[Fact]
73+
public void Constructor2_With_DropCreatedBeforeOnly_SetsDropCreatedBeforeAndDropAfterIsNull()
74+
{
75+
// Arrange & Act
76+
RetentionPolicyAttribute attr = new(dropCreatedBefore: "30 days");
77+
78+
// Assert
79+
Assert.Null(attr.DropAfter);
80+
Assert.Equal("30 days", attr.DropCreatedBefore);
81+
}
82+
83+
[Fact]
84+
public void Constructor2_With_BothSpecified_ThrowsInvalidOperationException()
85+
{
86+
// Arrange & Act & Assert
87+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
88+
() => new RetentionPolicyAttribute(dropAfter: "7 days", dropCreatedBefore: "30 days"));
89+
Assert.Contains("mutually exclusive", ex.Message);
90+
}
91+
92+
[Fact]
93+
public void Constructor2_With_NeitherSpecified_ThrowsInvalidOperationException()
94+
{
95+
// Arrange & Act & Assert
96+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
97+
() => new RetentionPolicyAttribute(dropAfter: null, dropCreatedBefore: null));
98+
Assert.Contains("exactly one", ex.Message, StringComparison.OrdinalIgnoreCase);
99+
}
100+
101+
#endregion
102+
103+
#region Default Values Tests
104+
105+
[Fact]
106+
public void Constructor1_With_Valid_DropAfter_SetsDefaultValues()
107+
{
108+
// Arrange & Act
109+
RetentionPolicyAttribute attr = new("7 days");
110+
111+
// Assert
112+
Assert.Equal(-1, attr.MaxRetries);
113+
Assert.Null(attr.DropCreatedBefore);
114+
Assert.Null(attr.InitialStart);
115+
Assert.Null(attr.ScheduleInterval);
116+
Assert.Null(attr.MaxRuntime);
117+
Assert.Null(attr.RetryPeriod);
118+
}
119+
120+
#endregion
121+
122+
#region Property Assignment Tests
123+
124+
[Fact]
125+
public void InitialStart_CanBeSet()
126+
{
127+
// Arrange
128+
RetentionPolicyAttribute attr = new("7 days")
129+
{
130+
// Act
131+
InitialStart = "2025-01-01T00:00:00Z"
132+
};
133+
134+
// Assert
135+
Assert.Equal("2025-01-01T00:00:00Z", attr.InitialStart);
136+
}
137+
138+
[Fact]
139+
public void ScheduleInterval_CanBeSet()
140+
{
141+
// Arrange
142+
RetentionPolicyAttribute attr = new("7 days")
143+
{
144+
// Act
145+
ScheduleInterval = "1 day"
146+
};
147+
148+
// Assert
149+
Assert.Equal("1 day", attr.ScheduleInterval);
150+
}
151+
152+
[Fact]
153+
public void MaxRuntime_CanBeSet()
154+
{
155+
// Arrange
156+
RetentionPolicyAttribute attr = new("7 days")
157+
{
158+
// Act
159+
MaxRuntime = "1 hour"
160+
};
161+
162+
// Assert
163+
Assert.Equal("1 hour", attr.MaxRuntime);
164+
}
165+
166+
[Fact]
167+
public void MaxRetries_CanBeSetToPositiveValue()
168+
{
169+
// Arrange
170+
RetentionPolicyAttribute attr = new("7 days")
171+
{
172+
// Act
173+
MaxRetries = 5
174+
};
175+
176+
// Assert
177+
Assert.Equal(5, attr.MaxRetries);
178+
}
179+
180+
[Fact]
181+
public void MaxRetries_CanBeSetToZero()
182+
{
183+
// Arrange
184+
RetentionPolicyAttribute attr = new("7 days")
185+
{
186+
// Act
187+
MaxRetries = 0
188+
};
189+
190+
// Assert
191+
Assert.Equal(0, attr.MaxRetries);
192+
}
193+
194+
[Fact]
195+
public void RetryPeriod_CanBeSet()
196+
{
197+
// Arrange
198+
RetentionPolicyAttribute attr = new("7 days")
199+
{
200+
// Act
201+
RetryPeriod = "30 minutes"
202+
};
203+
204+
// Assert
205+
Assert.Equal("30 minutes", attr.RetryPeriod);
206+
}
207+
208+
[Fact]
209+
public void AllProperties_CanBeSetTogether()
210+
{
211+
// Arrange
212+
RetentionPolicyAttribute attr = new("7 days")
213+
{
214+
// Act
215+
InitialStart = "2025-01-01T00:00:00Z",
216+
ScheduleInterval = "1 day",
217+
MaxRuntime = "1 hour",
218+
MaxRetries = 3,
219+
RetryPeriod = "30 minutes"
220+
};
221+
222+
// Assert
223+
Assert.Equal("7 days", attr.DropAfter);
224+
Assert.Equal("2025-01-01T00:00:00Z", attr.InitialStart);
225+
Assert.Equal("1 day", attr.ScheduleInterval);
226+
Assert.Equal("1 hour", attr.MaxRuntime);
227+
Assert.Equal(3, attr.MaxRetries);
228+
Assert.Equal("30 minutes", attr.RetryPeriod);
229+
}
230+
231+
#endregion
232+
233+
#region Constructor2 Empty and Whitespace String Tests
234+
235+
[Fact]
236+
public void Constructor2_With_Both_Empty_Strings_ThrowsInvalidOperationException()
237+
{
238+
// Arrange & Act & Assert
239+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
240+
() => new RetentionPolicyAttribute(dropAfter: "", dropCreatedBefore: ""));
241+
Assert.Contains("exactly one", ex.Message, StringComparison.OrdinalIgnoreCase);
242+
}
243+
244+
[Fact]
245+
public void Constructor2_With_Both_Whitespace_ThrowsInvalidOperationException()
246+
{
247+
// Arrange & Act & Assert
248+
InvalidOperationException ex = Assert.Throws<InvalidOperationException>(
249+
() => new RetentionPolicyAttribute(dropAfter: " ", dropCreatedBefore: " "));
250+
Assert.Contains("exactly one", ex.Message, StringComparison.OrdinalIgnoreCase);
251+
}
252+
253+
#endregion
254+
}

0 commit comments

Comments
 (0)