Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions VueApp/src/Effort/__tests__/course-import-dialog.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ describe("CourseImportDialog - Error Handling", () => {

// Simulate handling a non-Error value (e.g., from external code)
const nonErrorValue: unknown = "string error"
importError.value = nonErrorValue instanceof Error ? nonErrorValue.message : "Failed to import course"
const isError = typeof nonErrorValue === "object" && nonErrorValue instanceof Error
importError.value = isError ? (nonErrorValue as Error).message : "Failed to import course"

expect(importError.value).toBe("Failed to import course")
})
Expand All @@ -93,7 +94,8 @@ describe("CourseImportDialog - Error Handling", () => {

// Simulate handling a non-Error value (e.g., from external code)
const nonErrorValue: unknown = "unknown error"
searchError.value = nonErrorValue instanceof Error ? nonErrorValue.message : "Error searching for courses"
const isError = typeof nonErrorValue === "object" && nonErrorValue instanceof Error
searchError.value = isError ? (nonErrorValue as Error).message : "Error searching for courses"

expect(searchError.value).toBe("Error searching for courses")
})
Expand Down
13 changes: 3 additions & 10 deletions web/Areas/ClinicalScheduler/Controllers/RotationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,9 @@ public async Task<ActionResult<IEnumerable<RotationDto>>> GetRotations(int? serv
_logger.LogInformation("Getting rotations. ServiceId: {ServiceId}", serviceId);

// Get rotations through service layer
List<RotationDto> rotations;

if (serviceId.HasValue)
{
rotations = await _rotationService.GetRotationsByServiceAsync(serviceId.Value, HttpContext.RequestAborted);
}
else
{
rotations = await _rotationService.GetRotationsAsync(HttpContext.RequestAborted);
}
List<RotationDto> rotations = serviceId.HasValue
? await _rotationService.GetRotationsByServiceAsync(serviceId.Value, HttpContext.RequestAborted)
: await _rotationService.GetRotationsAsync(HttpContext.RequestAborted);

// Filter rotations based on user permissions
var allowedServiceIds = await GetAllowedServiceIdsAsync(HttpContext.RequestAborted);
Expand Down
19 changes: 6 additions & 13 deletions web/Areas/Effort/Services/EffortAuditService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,26 +600,19 @@

var auditRecordIds = auditQuery.Select(a => a.RecordId);

IQueryable<int> courseIds;

if (termCode.HasValue)
{
// When filtering by term, start from Records (very selective on TermCode)
// and use a semi-join for audit existence instead of a full Audits→Records join
courseIds = _context.Records
// When filtering by term, start from Records (very selective on TermCode)
// and use a semi-join for audit existence instead of a full Audits→Records join
IQueryable<int> courseIds = termCode.HasValue
? _context.Records
.AsNoTracking()
.Where(r => r.TermCode == termCode.Value)
.Where(r => auditRecordIds.Contains(r.Id))
.Select(r => r.CourseId)
.Distinct();
}
else
{
courseIds = auditQuery
.Distinct()
: auditQuery
.Join(_context.Records, a => a.RecordId, r => r.Id, (a, r) => r)
.Select(r => r.CourseId)
.Distinct();
}

return _context.Courses
.AsNoTracking()
Expand Down Expand Up @@ -669,7 +662,7 @@
(a.TableName == EffortAuditTables.Percentages && percentageIdsInDepts.Contains(a.RecordId)));
}

private IQueryable<Audit> BuildFilteredQuery(EffortAuditFilter filter)

Check warning on line 665 in web/Areas/Effort/Services/EffortAuditService.cs

View workflow job for this annotation

GitHub Actions / Backend Tests

'BuildFilteredQuery' has a cyclomatic complexity of '41'. Rewrite or refactor the code to decrease its complexity below '26'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1502)
{
var query = _context.Audits.AsNoTracking().AsQueryable();

Expand Down
18 changes: 8 additions & 10 deletions web/Areas/Effort/Services/MeritMultiYearService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -865,16 +865,14 @@ private static List<string> FilterTermsInRange(List<string> terms, int startTerm
/// </summary>
private static decimal? CalculateMedian(int n1, int n2, int n3, int n4, int n5)
{
var total = n1 + n2 + n3 + n4 + n5;
if (total == 0) return null;

// Build sorted response list
var responses = new List<decimal>();
for (var i = 0; i < n1; i++) responses.Add(1);
for (var i = 0; i < n2; i++) responses.Add(2);
for (var i = 0; i < n3; i++) responses.Add(3);
for (var i = 0; i < n4; i++) responses.Add(4);
for (var i = 0; i < n5; i++) responses.Add(5);
if (n1 + n2 + n3 + n4 + n5 == 0) return null;

var responses = Enumerable.Repeat(1m, n1)
.Concat(Enumerable.Repeat(2m, n2))
.Concat(Enumerable.Repeat(3m, n3))
.Concat(Enumerable.Repeat(4m, n4))
.Concat(Enumerable.Repeat(5m, n5))
.ToList();
Comment on lines +868 to +875
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In .NET, does Enumerable.Repeat<T>(element, count) throw an exception when count is negative? Please cite the official Microsoft docs.

💡 Result:

Yes, in.NET, the Enumerable.Repeat(TResult, Int32) method throws an ArgumentOutOfRangeException if the count parameter is less than 0 [1][2][3]. According to the official Microsoft documentation, the count argument represents the number of times to repeat the value in the generated sequence, and it must be a non-negative integer [1][2][3]. If a negative value is provided, the method explicitly throws an ArgumentOutOfRangeException [1][2][3].

Citations:


🏁 Script executed:

cd web/Areas/Effort/Services && wc -l MeritMultiYearService.cs

Repository: ucdavis/VIPER

Length of output: 87


🏁 Script executed:

cd web/Areas/Effort/Services && sed -n '850,900p' MeritMultiYearService.cs

Repository: ucdavis/VIPER

Length of output: 1732


🏁 Script executed:

cd web/Areas/Effort/Services && sed -n '1,50p' MeritMultiYearService.cs | head -50

Repository: ucdavis/VIPER

Length of output: 1734


🏁 Script executed:

cd web/Areas/Effort/Services && rg 'CalculateMedian' -n

Repository: ucdavis/VIPER

Length of output: 648


🏁 Script executed:

cd web/Areas/Effort/Services && sed -n '1,900p' MeritMultiYearService.cs | rg -B 10 'CalculateMedian\(' | head -100

Repository: ucdavis/VIPER

Length of output: 923


🏁 Script executed:

cd web/Areas/Effort/Services && sed -n '420,460p' MeritMultiYearService.cs

Repository: ucdavis/VIPER

Length of output: 1648


🏁 Script executed:

cd web/Areas/Effort/Services && sed -n '300,450p' MeritMultiYearService.cs | grep -n -B 30 'CalculateMedian'

Repository: ucdavis/VIPER

Length of output: 1296


🏁 Script executed:

cd web && find . -name '*.cs' -type f | xargs rg 'class EvalsMultiyearRow|record EvalsMultiyearRow|struct EvalsMultiyearRow' -A 20

Repository: ucdavis/VIPER

Length of output: 2139


🏁 Script executed:

cd web && rg 'EvalsMultiyearRow' --type cs | grep -i 'select\|new EvalsMultiyearRow'

Repository: ucdavis/VIPER

Length of output: 150


🏁 Script executed:

cd web && sed -n '1,1000p' Areas/Effort/Services/MeritMultiYearService.cs | grep -n -A 30 'new EvalsMultiyearRow'

Repository: ucdavis/VIPER

Length of output: 2066


Guard against negative bucket counts before Enumerable.Repeat.

Enumerable.Repeat(value, count) throws ArgumentOutOfRangeException when count < 0. The N1–N5 values come from database reads with NULL→0 conversion but no range validation. If the database contains corrupted negative counts, this refactor will crash instead of silently handling them. Clamp counts before calling Enumerable.Repeat():

Proposed fix
-        if (n1 + n2 + n3 + n4 + n5 == 0) return null;
-
-        var responses = Enumerable.Repeat(1m, n1)
-            .Concat(Enumerable.Repeat(2m, n2))
-            .Concat(Enumerable.Repeat(3m, n3))
-            .Concat(Enumerable.Repeat(4m, n4))
-            .Concat(Enumerable.Repeat(5m, n5))
+        n1 = Math.Max(0, n1);
+        n2 = Math.Max(0, n2);
+        n3 = Math.Max(0, n3);
+        n4 = Math.Max(0, n4);
+        n5 = Math.Max(0, n5);
+
+        if (n1 + n2 + n3 + n4 + n5 == 0) return null;
+
+        var responses = Enumerable.Repeat(1m, n1)
+            .Concat(Enumerable.Repeat(2m, n2))
+            .Concat(Enumerable.Repeat(3m, n3))
+            .Concat(Enumerable.Repeat(4m, n4))
+            .Concat(Enumerable.Repeat(5m, n5))
             .ToList();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (n1 + n2 + n3 + n4 + n5 == 0) return null;
var responses = Enumerable.Repeat(1m, n1)
.Concat(Enumerable.Repeat(2m, n2))
.Concat(Enumerable.Repeat(3m, n3))
.Concat(Enumerable.Repeat(4m, n4))
.Concat(Enumerable.Repeat(5m, n5))
.ToList();
n1 = Math.Max(0, n1);
n2 = Math.Max(0, n2);
n3 = Math.Max(0, n3);
n4 = Math.Max(0, n4);
n5 = Math.Max(0, n5);
if (n1 + n2 + n3 + n4 + n5 == 0) return null;
var responses = Enumerable.Repeat(1m, n1)
.Concat(Enumerable.Repeat(2m, n2))
.Concat(Enumerable.Repeat(3m, n3))
.Concat(Enumerable.Repeat(4m, n4))
.Concat(Enumerable.Repeat(5m, n5))
.ToList();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/Areas/Effort/Services/MeritMultiYearService.cs` around lines 868 - 875,
The code builds the responses list using Enumerable.Repeat with n1..n5 but
doesn't validate those counts; negative counts will throw
ArgumentOutOfRangeException. Before calling Enumerable.Repeat (the block that
constructs responses), clamp or sanitize the n1, n2, n3, n4, n5 values to a
non-negative integer (e.g., Math.Max(0, nX) or a small helper NormalizeCount)
and then use those sanitized variables when creating responses so
Enumerable.Repeat(...) never receives a negative count.


return CalculateListMedian(responses);
}
Expand Down
14 changes: 3 additions & 11 deletions web/Areas/RAPS/Services/UinformService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,26 +241,18 @@ public async Task<AdUser> GetUser(string? guid = null, string? userPrincipalName
request.Content = content;
}

UinformResponse<T>? uInformResponse;
using HttpResponseMessage response = await _httpClient.SendAsync(request);
string responseBody = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
uInformResponse = JsonSerializer.Deserialize<UinformResponse<T>>(responseBody, _jsonOptions);
}
else
{
uInformResponse = new UinformResponse<T>()
return response.IsSuccessStatusCode
? JsonSerializer.Deserialize<UinformResponse<T>>(responseBody, _jsonOptions)
: new UinformResponse<T>()
{
Success = false,
Error = new()
{
Message = response.StatusCode.ToString()
}
};
}

return uInformResponse;
}

private static string GetAuthSignature(HttpMethod method, string publicKey, int epochTime)
Expand Down
15 changes: 5 additions & 10 deletions web/Areas/Students/Services/GradYearClassLevel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,15 @@ static public Tuple<int, string> GetTermCodeAndClassLevelForGradYear(int gradYea
switch (termPart)
{
case 2:
termAndClassLevel = Tuple.Create(currentTerm, "V" + (4 - (gradYear - termYear)).ToString());
termAndClassLevel = Tuple.Create(currentTerm, "V" + (4 - (gradYear - termYear)));
break;
case 9:
termAndClassLevel = Tuple.Create(currentTerm, "V" + (5 - (gradYear - termYear)).ToString());
termAndClassLevel = Tuple.Create(currentTerm, "V" + (5 - (gradYear - termYear)));
break;
case 4:
if (gradYear - termYear == 1)
{
termAndClassLevel = Tuple.Create(currentTerm, "V4");
}
else
{
termAndClassLevel = Tuple.Create((termYear * 100) + 9, "V" + (5 - (gradYear - termYear)).ToString());
}
termAndClassLevel = gradYear - termYear == 1
? Tuple.Create(currentTerm, "V4")
: Tuple.Create((termYear * 100) + 9, "V" + (5 - (gradYear - termYear)));
break;
}

Expand Down
Loading