Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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