CSC/ECE 517 Spring 2026 - E2610. Teams hierarchy testing

From Expertiza_Wiki
Revision as of 23:58, 29 March 2026 by Awainga (talk | contribs) (Created page with "<!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...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

<!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>

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>.

Expertiza Teams Hierarchy
PlatformExpertiza (Ruby on Rails)
Root classTeam (abstract)
SubclassesCourseTeam, AssignmentTeam, MentoredTeam
MentorVihar Manojkumar Shah
Test frameworkRSpec
Membership modelEnrollment-based
Hierarchy depth3 levels (Team → AssignmentTeam → MentoredTeam)
Contents
  1. <a href="#overview">Overview</a>
  2. <a href="#hierarchy">Class hierarchy</a>
    1. <a href="#team">Team (abstract root)</a>
    2. <a href="#courseteam">CourseTeam</a>
    3. <a href="#assignmentteam">AssignmentTeam</a>
    4. <a href="#mentoredteam">MentoredTeam</a>
  3. <a href="#duty-vs-role">Duty vs. role distinction</a>
  4. <a href="#membership">Membership rules</a>
  5. <a href="#mentored-impl">MentoredTeam implementation</a>
  6. <a href="#testing">Test coverage (RSpec)</a>
    1. <a href="#test-model">Model validations</a>
    2. <a href="#test-assoc">Association integrity</a>
    3. <a href="#test-subclass">Subclass-specific behaviour</a>
    4. <a href="#test-capacity">Capacity and size limits</a>
    5. <a href="#test-edge">Edge cases</a>
    6. <a href="#test-auth">Controller authorization</a>
    7. <a href="#test-http">HTTP response codes</a>
  7. <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]

Role
Duty
     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 called user.role == 'Mentor', which tested a system-level permission and incorrectly excluded ordinary students serving as mentors. The corrected query is team_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 CourseTeam can be converted to an AssignmentTeam and vice versa, with member records preserved across the conversion.
  • Mentor assignment in MentoredTeam: Tests confirm that assign_mentor! sets the correct duty value, that mentor returns the right participant, and that querying duty (not role) 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]

<thead> </thead> <tbody> </tbody>
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

  1. Expertiza project documentation — Teams hierarchy testing. Mentor: Vihar Manojkumar Shah. Internal specification document.
  2. Fowler, M. (2002). Patterns of Enterprise Application Architecture. Addison-Wesley. (Single Table Inheritance pattern.)
  3. Rails Guides — Active Record Associations. <a href="https://guides.rubyonrails.org/association_basics.html">guides.rubyonrails.org</a>
  4. 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>