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
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.9",
"version": "10.0.10",
"commands": [
"dotnet-ef"
]
Expand All @@ -15,7 +15,7 @@
]
},
"jetbrains.resharper.globaltools": {
"version": "2026.1.3",
"version": "2026.2.0",
"commands": [
"jb"
]
Expand Down
8 changes: 4 additions & 4 deletions test/CTS/AssessmentControllerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,10 @@ public async Task GetAssessmentCheck()
var actrlAsChief = GetAssessmentController(SetupUsers.UserType.Chief);

var encounterIdExists = SetupAssessments.GetEncounters().First().EncounterId;
var encounterIdOtherFac = SetupAssessments.GetEncounters().Where(e => e.EnteredBy != SetupUsers.facultyUser.AaudUserId).First().EncounterId;
var encounterIdOtherStd = SetupAssessments.GetEncounters().Where(e => e.StudentUserId != SetupUsers.studentUser1.AaudUserId).First().EncounterId;
var encounterIdChief = SetupAssessments.GetEncounters().Where(e => e.ServiceId == SetupServices.ServiceChiefs[0].ServiceId).First().EncounterId;
var encounterIdNotChief = SetupAssessments.GetEncounters().Where(e => e.ServiceId != SetupServices.ServiceChiefs[0].ServiceId).First().EncounterId;
var encounterIdOtherFac = SetupAssessments.GetEncounters().First(e => e.EnteredBy != SetupUsers.facultyUser.AaudUserId).EncounterId;
var encounterIdOtherStd = SetupAssessments.GetEncounters().First(e => e.StudentUserId != SetupUsers.studentUser1.AaudUserId).EncounterId;
var encounterIdChief = SetupAssessments.GetEncounters().First(e => e.ServiceId == SetupServices.ServiceChiefs[0].ServiceId).EncounterId;
var encounterIdNotChief = SetupAssessments.GetEncounters().First(e => e.ServiceId != SetupServices.ServiceChiefs[0].ServiceId).EncounterId;
var encounterIdNotExists = 99999;

//act
Expand Down
6 changes: 3 additions & 3 deletions test/CTS/SetupAssessments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal static class SetupAssessments
FullName = SetupUsers.studentUser1.DisplayLastName + ", " + SetupUsers.studentUser1.DisplayFirstName,
MailId = "",
},
EnteredByPerson = SetupPeople.GetPeople().Where(p => p.PersonId == SetupUsers.facultyUser.AaudUserId).FirstOrDefault(),
EnteredByPerson = SetupPeople.GetPeople().FirstOrDefault(p => p.PersonId == SetupUsers.facultyUser.AaudUserId),
ServiceId = 1,
},
new Encounter
Expand All @@ -38,7 +38,7 @@ internal static class SetupAssessments
FullName = SetupUsers.studentUser1.DisplayLastName + ", " + SetupUsers.studentUser1.DisplayFirstName,
MailId = "",
},
EnteredByPerson = SetupPeople.GetPeople().Where(p => p.PersonId == SetupUsers.facultyUser.AaudUserId).FirstOrDefault(),
EnteredByPerson = SetupPeople.GetPeople().FirstOrDefault(p => p.PersonId == SetupUsers.facultyUser.AaudUserId),
ServiceId = 2,
},
new Encounter
Expand All @@ -53,7 +53,7 @@ internal static class SetupAssessments
FullName = SetupUsers.studentUser2.DisplayLastName + ", " + SetupUsers.studentUser2.DisplayFirstName,
MailId = "",
},
EnteredByPerson = SetupPeople.GetPeople().Where(p => p.PersonId == SetupUsers.otherFacultyUser.AaudUserId).FirstOrDefault(),
EnteredByPerson = SetupPeople.GetPeople().FirstOrDefault(p => p.PersonId == SetupUsers.otherFacultyUser.AaudUserId),
ServiceId = 3,
},
};
Expand Down
14 changes: 8 additions & 6 deletions test/Viper.test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,25 @@
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="10.0.10" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.2.3" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.8.1" />
<PackageReference Include="Microsoft.Testing.Platform" Version="2.3.2" />
<PackageReference Include="MockQueryable.NSubstitute" Version="10.0.8" />
<!-- Held at 5.3.0: NSubstitute 6.0.0 makes Arg.Is<T> take Predicate<T?>, emitting CS8602/CS8604
across the suite (upstream nsubstitute/NSubstitute#973, #976). Unpin once 6.0.1 ships. -->
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.27.0.140913">
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.30.0.144632">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<!-- Pins transitive SQLitePCLRaw past GHSA-2m69-gcr7-jv3q (EF Sqlite still references vulnerable 2.1.11); remove when EF's bundle catches up -->
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.4" />
<PackageReference Include="xunit.v3" Version="3.2.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="ClosedXML" Version="0.105.0" />
<PackageReference Include="Hangfire.Core" Version="1.8.23" />
<PackageReference Include="ClosedXML" Version="0.105.1" />
<PackageReference Include="Hangfire.Core" Version="1.8.24" />
<PackageReference Include="coverlet.collector" Version="10.0.1">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/Effort/Services/CourseClassificationService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public bool Is199299Course(string? crseNumb)
// Match 199 or 299 followed by non-digit or end of string
// Valid: "199", "299", "199A", "299R", "199B"
// Invalid: "1990", "2995", "19900"
return Regex.IsMatch(crseNumb.Trim(), @"^(199|299)([^0-9]|$)", RegexOptions.IgnoreCase);
return Regex.IsMatch(crseNumb.Trim(), @"^(199|299)([^0-9]|$)", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(1));
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions web/Areas/RAPS/Controllers/AdGroupRolesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ public AdGroupRolesController(RAPSContext context)
private bool GroupExists(int groupId)
{
var group = _context.OuGroups
.Where(gr => gr.OugroupId == groupId)
.FirstOrDefault();
.AsNoTracking()
.FirstOrDefault(gr => gr.OugroupId == groupId);
Comment thread
rlorenzo marked this conversation as resolved.
return group != null;
}

Expand Down
3 changes: 2 additions & 1 deletion web/Areas/RAPS/Services/RAPSCacheService.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Viper.Classes.SQLContext;
using Viper.Models.AAUD;

Expand All @@ -16,7 +17,7 @@ public RAPSCacheService(RAPSContext rapsContext, AAUDContext aaudContext, IUserH

public void ClearCachedRolesAndPermissionsForUser(string mothraId)
{
AaudUser? user = aaudContext.AaudUsers.Where(u => u.MothraId == mothraId).FirstOrDefault();
AaudUser? user = aaudContext.AaudUsers.AsNoTracking().FirstOrDefault(u => u.MothraId == mothraId);
if (user != null)
{
userHelper.ClearCachedRolesAndPermissions(user);
Expand Down
4 changes: 3 additions & 1 deletion web/Areas/RAPS/Services/VMACSExport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ join role in _RAPSContext.TblRoles on rm.RoleId equals role.RoleId
//Get a single record with their ids and blank role so that an empty permissions array can be pushed.
if (userList.Count == 0 && loginId.Length > 0)
{
var user = _RAPSContext.VwAaudUser.Where(a => a.LoginId == loginId).FirstOrDefault();
var user = _RAPSContext.VwAaudUser
.AsNoTracking()
.FirstOrDefault(a => a.LoginId == loginId);
if (user != null)
{
userList.Add(new UserList
Expand Down
4 changes: 2 additions & 2 deletions web/Areas/RAPS/Views/Groups/List.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@
},
async mounted() {
this.groupTable.load(this)
this.ouGroups = await viperFetch(this, "Groups/OU")
this.ouGroups = (await viperFetch(this, "Groups/OU")) ?? []
this.ouGroups = this.ouGroups
.map(g => ({ label: "OU: " + g.cn, value: g.distinguishedName }))

this.adGroups = await viperFetch(this, "Groups/AD")
this.adGroups = (await viperFetch(this, "Groups/AD")) ?? []
this.adGroups = this.adGroups
.map(g => ({ label: "AD: " + (g.displayName ?? g.samAccountName), value: g.distinguishedName }))

Expand Down
6 changes: 3 additions & 3 deletions web/Areas/RAPS/Views/Groups/Members.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
</q-card-section>
</q-card>
</q-dialog>
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Sync Members" @@click="syncGroup()"></q-btn>
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Edit Members" :href="'RoleMembers?roleId=' + group.groupRoleId"></q-btn>
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Sync Members" :disable="!group.groupId" @@click="syncGroup()"></q-btn>
<q-btn no-caps color="primary" padding="xs md" class="q-mr-md q-mb-sm" label="Edit Members" :disable="!group.groupRoleId" :href="'RoleMembers?roleId=' + group.groupRoleId"></q-btn>
<q-table dense
ref="membersTable"
row-key="memberId"
Expand Down Expand Up @@ -113,7 +113,7 @@
},
methods: {
loadGroup: async function () {
this.group = await viperFetch(this, "Groups/" + this.groupId)
this.group = (await viperFetch(this, "Groups/" + this.groupId)) ?? {}
Comment thread
rlorenzo marked this conversation as resolved.
},
fdate: function (d) {
return formatDate(d)
Expand Down
4 changes: 3 additions & 1 deletion web/Areas/RAPS/Views/Groups/Roles.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@
},
methods: {
loadGroup: async function () {
this.group = await viperFetch(this, "Groups/" + this.groupId)
// viperFetch resolves to undefined on 4xx/5xx; keep group an object
// so the {{group.name}} heading renders blank instead of throwing.
this.group = (await viperFetch(this, "Groups/" + this.groupId)) ?? {}
},
addGroupRole: async function (groupRole) {
viperFetch(this,
Expand Down
10 changes: 7 additions & 3 deletions web/Areas/RAPS/Views/Members/History.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,17 @@
},
methods: {
getAuditLog: async function() {
this.auditLog = await viperFetch(this, "members/" + this.memberId + "/history?startDate=" + this.startDate.value)
// The date select is clearable, so startDate can be null; omit the
// filter entirely in that case to show the full history.
const startDate = this.startDate?.value
const query = startDate ? "?startDate=" + encodeURIComponent(startDate) : ""
this.auditLog = (await viperFetch(this, "members/" + this.memberId + "/history" + query)) ?? []
}
},
async mounted() {
this.memberId = this.urlParams.get("memberId")
this.member = await viperFetch(this, "members/" + this.memberId)
this.historyDates = (await viperFetch(this, "members/" + this.memberId + "/history/dates"))
this.member = (await viperFetch(this, "members/" + this.memberId)) ?? {}
this.historyDates = ((await viperFetch(this, "members/" + this.memberId + "/history/dates")) ?? [])
.map(d => {
Comment thread
rlorenzo marked this conversation as resolved.
return {label: formatDate(d), value: d}
})
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Members/Permissions.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@
this.selectedPermissions = []
},
loadMember: async function () {
this.member = await viperFetch(this, "members/" + this.memberId)
this.member = (await viperFetch(this, "members/" + this.memberId)) ?? {}
}
},
mounted() {
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Members/RSOP.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
methods: {
loadMember: async function () {
this.member = await viperFetch(this, "Members/" + this.memberId)
this.member = (await viperFetch(this, "Members/" + this.memberId)) ?? {}
}
},
async mounted() {
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Members/Roles.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@
this.selectedRoles = []
},
loadMember: async function () {
this.member = await viperFetch(this, "Members/" + this.memberId)
this.member = (await viperFetch(this, "Members/" + this.memberId)) ?? {}
},
pushToVMACS: async function (skipToggleCheck) {
if (skipToggleCheck || this.updateVMACS) {
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Permissions/AllMembers.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
},
methods: {
loadPermission: async function () {
this.permission = await viperFetch(this, "Permissions/" + this.permissionId)
this.permission = (await viperFetch(this, "Permissions/" + this.permissionId)) ?? {}
}
},
async mounted() {
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Permissions/Members.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@
},
methods: {
loadPermission: async function () {
this.permission = await viperFetch(this, "Permissions/" + this.permissionId)
this.permission = (await viperFetch(this, "Permissions/" + this.permissionId)) ?? {}
},
memberSearch: function (val, update, abort) {
if (val.length < 3) {
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Permissions/Roles.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
},
methods: {
loadPermission: async function () {
this.permission = await viperFetch(this, "Permissions/" + this.permissionId)
this.permission = (await viperFetch(this, "Permissions/" + this.permissionId)) ?? {}
},
updateRolePermissions: async function (allowAccess = 1) {
var roleIds = this.selectedPermissions.reduce( (ids, sp) => {ids.push(sp.roleId); return ids;}, [] )
Expand Down
2 changes: 1 addition & 1 deletion web/Areas/RAPS/Views/Permissions/RolesRO.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
methods: {
loadPermission: async function () {
this.permission = await viperFetch(this, "Permissions/" + this.permissionId)
this.permission = (await viperFetch(this, "Permissions/" + this.permissionId)) ?? {}
},
loadTables: async function () {
await this.rolePermissionTable.load(this)
Expand Down
11 changes: 9 additions & 2 deletions web/Areas/RAPS/Views/Roles/ApplyTemplate.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<tr>
<th class="text-left"><span class="text-h6">Review Changes</span></th>
<th colspan="2" class="text-right">
<q-btn label="Apply template to user(s)" color="primary" no-caps @@click="applyTemplate(true)" class="q-px-sm q-py-xs"></q-btn>
<q-btn label="Apply template to user(s)" color="primary" no-caps :disable="!templateLoaded" @@click="applyTemplate(true)" class="q-px-sm q-py-xs"></q-btn>
</th>
</tr>
</thead>
Expand Down Expand Up @@ -50,6 +50,8 @@
membersFound: false, //show table if a matching user was found
roleTemplateId: 0,
roleTemplate: {},
// Starts false so applying is disabled until the template is read.
templateLoaded: false,
loginids: "",
userSearch: "",
userSearchResults: [],
Expand Down Expand Up @@ -142,7 +144,12 @@
},
async mounted() {
this.roleTemplateId = this.urlParams.get("roleTemplateId")
this.roleTemplate = await viperFetch(this, "RoleTemplates/" + this.roleTemplateId)
// roleTemplateRoles must be an array: the VMACS push path calls .reduce() on it.
// Track load failure separately so an unread template is never mistaken for
// an empty one, which would push an empty role list to VMACS.
const template = await viperFetch(this, "RoleTemplates/" + this.roleTemplateId)
this.templateLoaded = template !== undefined
this.roleTemplate = template ?? { roleTemplateRoles: [] }
this.VMACSPush = this.urlPath.indexOf("/VMACS.") > 0
},
watch: {
Expand Down
27 changes: 20 additions & 7 deletions web/Areas/RAPS/Views/Roles/DelegateRoles.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
:pagination="{rowsPerPage:0}"
:loading="loadingRoles">
<template v-slot:top-left>
<q-btn dense no-caps color="primary" padding="xs md" @@click="submitChanges()" label="Submit changes"></q-btn>
<q-btn dense no-caps color="primary" padding="xs md" :disable="!rolesLoaded" @@click="submitChanges()" label="Submit changes"></q-btn>
</template>
</q-table>

Expand All @@ -26,6 +26,8 @@
roles: [],
selectedRoles: [],
loadingRoles: false,
// Starts false so the page is disabled until the initial read succeeds.
rolesLoaded: false,
columns: [
{ name: "role", label: "Role", field: "friendlyName", align: "left" },
{ name: "description", label: "Description", field: "description", align: "left" }
Expand All @@ -34,13 +36,24 @@
},
methods: {
loadRoles: async function() {
this.rolesLoaded = false
var childRoles = await viperFetch(this, "Roles/ControlledBy/" + + this.urlParams.get("roleId"))
var allRoles = (await viperFetch(this, "Roles?Application=0"))
.filter(r => childRoles.find(cr => cr.roleId === r.roleId) === undefined)
this.selectedRoles = childRoles;//.reduce((result, cr) => { result.push(cr.roleId); return result }, [])
this.roles = childRoles.concat(allRoles)
var allRoles = await viperFetch(this, "Roles?Application=0")
// A failed or still-pending read must not look like "nothing is
// selected": submitting that would PUT an empty list and wipe the
// existing delegations. Stay disabled until both reads succeed.
if (childRoles === undefined || allRoles === undefined) {
return
}
this.selectedRoles = childRoles
this.roles = childRoles.concat(
allRoles.filter(r => childRoles.find(cr => cr.roleId === r.roleId) === undefined))
this.rolesLoaded = true
},
submitChanges: async function() {
if (!this.rolesLoaded) {
return
}
var roleIds = this.selectedRoles.reduce( (result, role) => {
result.push(role.roleId)
return result
Expand All @@ -57,8 +70,8 @@
}
},
async mounted() {
this.loadRoles()
this.role = await viperFetch(this, "Roles/" + this.urlParams.get("roleId"))
await this.loadRoles()
this.role = (await viperFetch(this, "Roles/" + this.urlParams.get("roleId"))) ?? {}

}
})
Expand Down
3 changes: 2 additions & 1 deletion web/Areas/RAPS/Views/Roles/Members.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@
},
methods: {
loadRole: async function () {
this.role = await viperFetch(this, "Roles/" + this.roleId)
// instance must be a string: mounted() calls role.instance.indexOf().
this.role = (await viperFetch(this, "Roles/" + this.roleId)) ?? { instance: "" }
},
memberSearch: function (val, update, abort) {
if (val.length < 3) {
Expand Down
Loading
Loading