CSC/ECE 517 Spring 2026 - E2610. Teams hierarchy testing
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Expertiza Teams Hierarchy – Wikipedia-style Article</title> <style>
@import url('https://fonts.googleapis.com/css2?family=Linux+Libertine+Display:ital,wght@0,400;0,600;1,400&family=Linux+Biolinum:wght@400;600&display=swap');
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #f8f9fa;
--surface: #ffffff;
--border: #a2a9b1;
--border-light: #eaecf0;
--text: #202122;
--text-muted: #54595d;
--text-caption: #72777d;
--link: #3366cc;
--link-visited: #6b4ba1;
--toc-bg: #f8f9fa;
--toc-border: #a2a9b1;
--infobox-bg: #f8f9fa;
--infobox-header: #cee0f2;
--note-bg: #eaf3fb;
--note-border: #3683bb;
--warn-bg: #fef6e7;
--warn-border: #f28500;
--code-bg: #f6f6f6;
--title-border: #a2a9b1;
}
body {
font-family: 'Linux Libertine', 'Georgia', serif;
font-size: 14.4px;
line-height: 1.6;
color: var(--text);
background: var(--bg);
}
.page-wrap {
max-width: 960px;
margin: 0 auto;
background: var(--surface);
padding: 20px 24px 40px;
border-left: 1px solid var(--border-light);
border-right: 1px solid var(--border-light);
min-height: 100vh;
}
/* ── Header ── */
.page-header {
border-bottom: 1px solid var(--title-border);
margin-bottom: 16px;
padding-bottom: 4px;
}
h1.page-title {
font-family: 'Linux Libertine Display', 'Georgia', serif;
font-size: 2em;
font-weight: 400;
color: var(--text);
letter-spacing: -0.3px;
}
.hatnote {
font-style: italic;
color: var(--text-muted);
font-size: 0.9em;
margin-bottom: 12px;
padding-left: 1.6em;
border-left: 3px solid var(--border-light);
}
/* ── Sections ── */
h2 {
font-family: 'Linux Biolinum', 'Arial', sans-serif;
font-size: 1.4em;
font-weight: 600;
border-bottom: 1px solid var(--border-light);
margin: 28px 0 10px;
padding-bottom: 4px;
color: var(--text);
}
h3 {
font-family: 'Linux Biolinum', 'Arial', sans-serif;
font-size: 1.1em;
font-weight: 600;
margin: 18px 0 8px;
color: var(--text);
}
p { margin-bottom: 10px; }
a { color: var(--link); text-decoration: none; }
a:hover { text-decoration: underline; }
a:visited { color: var(--link-visited); }
/* ── Table of Contents ── */
.toc {
float: right;
clear: right;
margin: 0 0 20px 24px;
background: var(--toc-bg);
border: 1px solid var(--toc-border);
padding: 12px 16px;
font-size: 0.88em;
min-width: 220px;
max-width: 280px;
}
.toc-title {
font-weight: bold;
text-align: center;
margin-bottom: 8px;
font-size: 1em;
}
.toc ol {
margin: 0;
padding-left: 20px;
}
.toc li { margin: 3px 0; line-height: 1.4; }
.toc a { color: var(--text); }
.toc a:hover { text-decoration: underline; }
.toc ol ol { font-size: 0.95em; margin-top: 3px; }
/* ── Infobox ── */
.infobox {
float: right;
clear: right;
margin: 0 0 20px 24px;
border: 1px solid var(--border);
background: var(--infobox-bg);
font-size: 0.88em;
min-width: 260px;
max-width: 300px;
}
.infobox-title {
background: var(--infobox-header);
text-align: center;
padding: 8px 10px;
font-weight: bold;
font-family: 'Linux Biolinum', sans-serif;
font-size: 1.05em;
border-bottom: 1px solid var(--border);
}
.infobox table {
width: 100%;
border-collapse: collapse;
}
.infobox td {
padding: 4px 8px;
vertical-align: top;
border-bottom: 1px solid var(--border-light);
line-height: 1.4;
}
.infobox td:first-child {
font-weight: bold;
color: var(--text-muted);
white-space: nowrap;
width: 40%;
}
/* ── Notes / Warnings ── */
.note {
background: var(--note-bg);
border-left: 4px solid var(--note-border);
padding: 10px 14px;
margin: 14px 0;
font-size: 0.93em;
}
.warn {
background: var(--warn-bg);
border-left: 4px solid var(--warn-border);
padding: 10px 14px;
margin: 14px 0;
font-size: 0.93em;
}
/* ── Code ── */
code {
font-family: 'Courier New', monospace;
background: var(--code-bg);
border: 1px solid var(--border-light);
padding: 1px 4px;
font-size: 0.9em;
border-radius: 2px;
}
pre {
background: var(--code-bg);
border: 1px solid var(--border-light);
padding: 12px 14px;
overflow-x: auto;
font-size: 0.88em;
line-height: 1.5;
margin: 12px 0;
}
pre code { background: none; border: none; padding: 0; font-size: inherit; }
/* ── Tables ── */
.wiki-table {
border-collapse: collapse;
margin: 16px 0;
font-size: 0.9em;
width: 100%;
}
.wiki-table th {
background: #eaecf0;
border: 1px solid var(--border);
padding: 6px 10px;
text-align: left;
font-family: 'Linux Biolinum', sans-serif;
}
.wiki-table td {
border: 1px solid var(--border);
padding: 6px 10px;
vertical-align: top;
}
.wiki-table tr:nth-child(even) td { background: #f8f9fa; }
/* ── Hierarchy diagram ── */
.hierarchy-wrap {
margin: 18px 0;
overflow-x: auto;
}
/* ── Categories ── */
.categories {
border-top: 1px solid var(--border-light);
margin-top: 36px;
padding-top: 10px;
font-size: 0.88em;
color: var(--text-muted);
}
.categories strong { color: var(--text); }
/* ── References ── */
.references {
font-size: 0.88em;
color: var(--text-muted);
columns: 2;
column-gap: 24px;
}
.references ol { padding-left: 18px; }
.references li { margin-bottom: 4px; line-height: 1.4; }
.ref-sup {
font-size: 0.75em;
vertical-align: super;
color: var(--link);
cursor: pointer;
}
/* ── Clearfix ── */
.clearfix::after { content: ; display: table; clear: both; }
/* ── Responsive ── */
@media (max-width: 640px) {
.infobox, .toc { float: none; max-width: 100%; margin: 0 0 16px; }
.references { columns: 1; }
}
/* ── Duty vs Role comparison box ── */
.compare-box {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
border: 1px solid var(--border);
margin: 16px 0;
font-size: 0.9em;
}
.compare-box .col-head {
background: #eaecf0;
font-weight: bold;
padding: 7px 12px;
border-bottom: 1px solid var(--border);
font-family: 'Linux Biolinum', sans-serif;
}
.compare-box .col-head:first-child { border-right: 1px solid var(--border); }
.compare-box .col-body {
padding: 8px 12px;
line-height: 1.5;
}
.compare-box .col-body:first-child { border-right: 1px solid var(--border); }
ul, ol { padding-left: 22px; margin-bottom: 10px; }
li { margin-bottom: 3px; }
</style> </head> <body>
Expertiza Teams Hierarchy
This article covers the software design and implementation of team management in the Expertiza platform. For the broader Expertiza system, see <a href="#">Expertiza (software)</a>.
| Platform | Expertiza (Ruby on Rails) |
| Root class | Team (abstract) |
| Subclasses | CourseTeam, AssignmentTeam, MentoredTeam |
| Mentor | Vihar Manojkumar Shah |
| Test framework | RSpec |
| Membership model | Enrollment-based |
| Hierarchy depth | 3 levels (Team → AssignmentTeam → MentoredTeam) |
- <a href="#overview">Overview</a>
- <a href="#hierarchy">Class hierarchy</a>
- <a href="#team">Team (abstract root)</a>
- <a href="#courseteam">CourseTeam</a>
- <a href="#assignmentteam">AssignmentTeam</a>
- <a href="#mentoredteam">MentoredTeam</a>
- <a href="#duty-vs-role">Duty vs. role distinction</a>
- <a href="#membership">Membership rules</a>
- <a href="#mentored-impl">MentoredTeam implementation</a>
- <a href="#testing">Test coverage (RSpec)</a>
- <a href="#test-model">Model validations</a>
- <a href="#test-assoc">Association integrity</a>
- <a href="#test-subclass">Subclass-specific behaviour</a>
- <a href="#test-capacity">Capacity and size limits</a>
- <a href="#test-edge">Edge cases</a>
- <a href="#test-auth">Controller authorization</a>
- <a href="#test-http">HTTP response codes</a>
- <a href="#references">References</a>
The Expertiza teams hierarchy is a structured set of Ruby on Rails model classes that governs how student groups are formed, managed, and constrained within the <a href="#">Expertiza</a> peer-review platform. The hierarchy consists of exactly four classes — Team, CourseTeam, AssignmentTeam, and MentoredTeam — and underpins every form of collaborative participation in the system, from reserving discussion topics to submitting completed assignments.
The design follows a classical object-oriented inheritance pattern: an intentionally abstract superclass provides the shared data model and common behaviours, while concrete subclasses carry distinct responsibilities, lifecycle rules, and membership constraints. The hierarchy also plays a central role in the platform's role and duty model, most visibly in the MentoredTeam class, which designates a team participant as a mentor through a duty attribute rather than a system-level role.
Overview
Expertiza organises student collaboration through a structured class hierarchy dedicated to managing teams. Teams serve as the fundamental unit of academic participation: they allow groups of students to collectively reserve discussion topics, collaborate on coursework, and submit completed assignments for evaluation.[1]
Two broad kinds of team exist in the system. Course teams persist for the duration of an entire course and may be reused across multiple assignments, particularly in Team-Based Learning (TBL) environments. Assignment teams are formed specifically for a single assignment and dissolve once that assignment closes. When an assignment is configured to automatically assign mentors, the team is instantiated as a MentoredTeam — a specialised subclass of AssignmentTeam that records a designated mentor guiding the group.
Class hierarchy
The four classes form a three-level inheritance tree:
<svg width="100%" viewBox="0 0 680 260" xmlns="http://www.w3.org/2000/svg" style="max-width:600px;display:block;margin:0 auto"> <defs> <marker id="arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"> <path d="M2 1L8 5L2 9" fill="none" stroke="#54595d" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/> </marker> </defs> <rect x="230" y="18" width="220" height="52" rx="6" fill="#cee0f2" stroke="#a2a9b1" stroke-width="1"/> <text x="340" y="40" text-anchor="middle" font-family="'Courier New',monospace" font-size="14" font-weight="600" fill="#202122">Team</text> <text x="340" y="58" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#54595d">(abstract superclass)</text>
<line x1="280" y1="70" x2="140" y2="130" stroke="#54595d" stroke-width="1" marker-end="url(#arrow)"/>
<line x1="400" y1="70" x2="500" y2="130" stroke="#54595d" stroke-width="1" marker-end="url(#arrow)"/>
<rect x="40" y="130" width="200" height="52" rx="6" fill="#eaf3fb" stroke="#a2a9b1" stroke-width="1"/>
<text x="140" y="152" text-anchor="middle" font-family="'Courier New',monospace" font-size="13" font-weight="600" fill="#202122">CourseTeam</text>
<text x="140" y="170" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#54595d">Course-scoped teams</text>
<rect x="390" y="130" width="220" height="52" rx="6" fill="#eaf3fb" stroke="#a2a9b1" stroke-width="1"/>
<text x="500" y="152" text-anchor="middle" font-family="'Courier New',monospace" font-size="13" font-weight="600" fill="#202122">AssignmentTeam</text>
<text x="500" y="170" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#54595d">Assignment-scoped teams</text>
<line x1="500" y1="182" x2="500" y2="208" stroke="#54595d" stroke-width="1" marker-end="url(#arrow)"/>
<rect x="380" y="208" width="240" height="42" rx="6" fill="#fef6e7" stroke="#f28500" stroke-width="1"/>
<text x="500" y="226" text-anchor="middle" font-family="'Courier New',monospace" font-size="13" font-weight="600" fill="#202122">MentoredTeam</text>
<text x="500" y="242" text-anchor="middle" font-family="Arial,sans-serif" font-size="11" fill="#54595d">Includes a duty=mentor participant</text>
<path d="M240 156 Q310 195 390 156" fill="none" stroke="#3366cc" stroke-width="1" stroke-dasharray="5,4"/>
<text x="315" y="205" text-anchor="middle" font-family="Arial,sans-serif" font-size="10" fill="#3366cc">convertible</text>
</svg>
Team (abstract root)
The Team class sits at the root of the hierarchy and provides the shared data model, associations, and common behaviours that all team types inherit. Team is intentionally abstract — it is never meant to be instantiated directly in production or test code. All functional teams that appear in the application must be concrete instances of one of the three subclasses. This design enforces a clean separation between shared infrastructure and specific business logic, and it means that any attempt to create a raw Team object should be treated as a programming error.[1]
Constraint: Instantiating Team directly is a programming error. The test suite must explicitly verify that this constraint is honoured.
CourseTeam
CourseTeam instances remain intact throughout a course, collaborating on all assignments within it. While some instructors opt not to use them, others find CourseTeams beneficial, especially in Team-Based Learning environments. These teams can be converted into AssignmentTeams, and vice versa, as instructional needs change.[1]
A student may only be added to a CourseTeam if they are a participant in the associated course. No user should appear on more than one course team within the same course.
AssignmentTeam
AssignmentTeam instances are formed specifically for individual assignments. Membership is restricted to users who are already participants in the associated assignment. The class is the direct superclass of MentoredTeam and participates in the bidirectional conversion relationship with CourseTeam.
MentoredTeam
When an assignment is configured to automatically assign mentors, teams are instantiated as MentoredTeam — a subclass of AssignmentTeam with a designated mentor guiding the group. The mentor is identified not by a system role but by a duty attribute on the team membership record (see <a href="#duty-vs-role">§ Duty vs. role distinction</a>).
Duty vs. role distinction
A critical design point of the Expertiza platform is the separation between a user's role and their duty within a team. The current system's incorrect implementation of MentoredTeam conflates these two concepts; the corrected implementation must use duty exclusively when identifying the mentor participant.[1]
A system-level permission level assigned to a user account.
Examples:Instructor,TeachingAssistant,Student
A function assigned to a user within a specific team for a specific assignment.
Examples:Submitter,Reader,Reviewer,Mentor
The incorrect implementation checked whether a participant held the role of Mentor at the system level. This was incorrect because a mentor is not a privileged system account — they are an ordinary user who has been assigned the duty of Mentor on a particular MentoredTeam. The corrected implementation queries the duty attribute of the team participant record rather than the role attribute of the user.
Membership rules
A core correctness requirement of the entire hierarchy is that team membership must always be grounded in valid participation:[1]
<thead> </thead> <tbody> </tbody>| Rule | Applies to | Enforcement |
|---|---|---|
| A student may only be added if they are a participant in the associated course. | CourseTeam |
Model validation; raises error on violation |
| A student may only be added if they are a participant in the associated assignment. | AssignmentTeam, MentoredTeam |
Model validation; raises error on violation |
| No user may appear on multiple course teams within the same course. | CourseTeam |
Uniqueness constraint at model and DB level |
| No user may appear on multiple assignment teams within the same assignment. | AssignmentTeam, MentoredTeam |
Uniqueness constraint at model and DB level |
| Team size must not exceed the configured maximum capacity. | All subclasses | Rejected with error before save |
MentoredTeam implementation
The corrected MentoredTeam implementation locates the mentor participant by querying the duty attribute of team member records, not the system-level role of the associated user. A representative model method looks as follows:
<code># app/models/mentored_team.rb
class MentoredTeam < AssignmentTeam
# Returns the TeamMember record whose duty is 'Mentor'.
def mentor
team_members.find_by(duty: 'Mentor')
end
# Assigns a participant as the team mentor by setting their duty.
def assign_mentor!(user)
member = team_members.find_by(user: user)
raise ArgumentError, 'User is not a member of this team' unless member
member.update!(duty: 'Mentor')
end
end</code>
Note: Earlier versions of this class calleduser.role == 'Mentor', which tested a system-level permission and incorrectly excluded ordinary students serving as mentors. The corrected query isteam_members.find_by(duty: 'Mentor').
Test coverage (RSpec)
Comprehensive RSpec tests are required to validate the hierarchy. The test suite is organised into seven areas of concern.
Model validations
Tests confirm that required fields and uniqueness constraints are enforced. This includes verifying that a Team cannot be instantiated directly, that each subclass requires its parent association (course or assignment), and that enrollment-based membership rules reject non-participants.[1]
<code>RSpec.describe CourseTeam, type: :model do
it 'is invalid without an associated course' do
team = build(:course_team, course: nil)
expect(team).not_to be_valid
end
it 'rejects a member not enrolled in the course' do
team = create(:course_team)
student = create(:user) # no enrollment record
expect { team.add_member(student) }.to raise_error(ActiveRecord::RecordInvalid)
end
end</code>
Association integrity
Tests verify that teams are correctly linked to their parent course or assignment and that membership records are properly scoped. For example, querying a team's members must not leak records belonging to teams in other courses or assignments.
Subclass-specific behaviour
This section covers two areas unique to the hierarchy design:
- Bidirectional conversion: A
CourseTeamcan be converted to anAssignmentTeamand vice versa, with member records preserved across the conversion. - Mentor assignment in
MentoredTeam: Tests confirm thatassign_mentor!sets the correctdutyvalue, thatmentorreturns the right participant, and that queryingduty(notrole) is the mechanism used.
<code>RSpec.describe MentoredTeam, type: :model do
it 'identifies the mentor by duty, not by role' do
team = create(:mentored_team)
student = create(:user, role: 'Student') # system role is Student
create(:team_member, team: team, user: student, duty: 'Mentor')
expect(team.mentor.user).to eq student
end
end</code>
Capacity and size limits
Tests ensure that teams cannot exceed their configured maximum member count, and that appropriate errors are returned when this limit is breached.
<code>it 'rejects a member when the team is at maximum capacity' do
team = create(:assignment_team, max_members: 2)
create_list(:team_member, 2, team: team)
extra = create(:enrolled_user, assignment: team.assignment)
expect { team.add_member(extra) }.to raise_error(TeamFullError)
end</code>
Edge cases
Edge-case tests cover the following scenarios:
- Performing operations on a team with zero members.
- Attempting to add an already-enrolled member to the same team (duplicate membership).
- Creating teams for assignments that have no participants yet.
Controller authorization
Tests for TeamsController verify the correct role-based access policy:
- Students are blocked from viewing other teams.
- Mentors are restricted to their own mentored teams.
- Instructors have unrestricted access to all teams.
<code>describe 'GET #index' do
context 'when the current user is a student' do
before { sign_in create(:student) }
it 'is forbidden from viewing other teams' do
get :index, params: { assignment_id: assignment.id }
expect(response).to have_http_status(:forbidden)
end
end
end</code>
HTTP response codes
Tests confirm that unauthorized access attempts return appropriate HTTP status codes — specifically 403 Forbidden — rather than silently succeeding or returning a 500 Internal Server Error.[1]
| Actor | Action | Expected HTTP status |
|---|---|---|
| Student (not on team) | View another team's details | 403 Forbidden |
| Mentor (wrong team) | Access a MentoredTeam they do not mentor | 403 Forbidden |
| Instructor | Any team action | 200 OK |
| Unauthenticated user | Any team action | 401 Unauthorized |
References
- Expertiza project documentation — Teams hierarchy testing. Mentor: Vihar Manojkumar Shah. Internal specification document.
- Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley. (Single Table Inheritance pattern.)
- Rails Guides — Active Record Associations. <a href="https://guides.rubyonrails.org/association_basics.html">guides.rubyonrails.org</a>
- RSpec Documentation — RSpec Rails. <a href="https://rspec.info/documentation/6.0/rspec-rails/">rspec.info</a>
Categories: <a href="#">Software design patterns</a> · <a href="#">Ruby on Rails</a> · <a href="#">Peer review systems</a> · <a href="#">Object-oriented programming</a> · <a href="#">Team-Based Learning</a> · <a href="#">RSpec</a>
</body> </html>