From da5f68703756ac584aad56dbc1f2d3556c3ef4d7 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Tue, 13 Jan 2026 15:26:35 -0600 Subject: [PATCH 1/8] Basic Testing Functionality + Test groupParens --- parser/requisiteParser_test.go | 87 ++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 parser/requisiteParser_test.go diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go new file mode 100644 index 0000000..dd1b027 --- /dev/null +++ b/parser/requisiteParser_test.go @@ -0,0 +1,87 @@ +package parser + +import "testing" + +func TestInitMatchers(t *testing.T) { + // Test 1: Initialization + Matchers = nil + initMatchers() + + if Matchers == nil { + t.Error("Matchers should not be nil after initialization") + } + + if Matchers == nil { + t.Error("Matchers contain matchers after initialization") + } + + // Test 2: No regex compilation errors + for i, m := range Matchers { + if m.Regex == nil { + t.Errorf("Matcher %d has nil regex", i) + } + } +} + +func TestGroupParens(t *testing.T) { + tests := []struct { + name string + input string + expected string + groups []string + }{ + { + name: "No parentheses", + input: "MATH 2417 and PHYS 2125", + expected: "MATH 2417 and PHYS 2125", + groups: []string{}, + }, + { + name: "Single parentheses", + input: "MATH 2417 and (PHYS 2125 or PHYS 2126)", + expected: "MATH 2417 and @0", + groups: []string{"PHYS 2125 or PHYS 2126"}, + }, + { + name: "Nested parentheses", + input: "((A and B) or (C and D))", + expected: "@0", + groups: []string{"@1 or @2", "A and B", "C and D"}, + }, + { + name: "Multiple parentheses", + input: "(A) and (B) or (C)", + expected: "@0 and @1 or @2", + groups: []string{"A", "B", "C"}, + }, + { + name: "Mismatched closing parentheses", + input: "(A and B)) extra text", + expected: "@0 extra text", + groups: []string{"A and B"}, + }, + { + name: "Complex expression", + input: "MATH 2417 and (PHYS 2125 or (PHYS 2126 and CHEM 1311))", + expected: "MATH 2417 and @0", + groups: []string{"PHYS 2125 or @1", "PHYS 2126 and CHEM 1311"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, groups := groupParens(tt.input) + if result != tt.expected { + t.Errorf("groupParens() = %q, want %q", result, tt.expected) + } + if len(groups) != len(tt.groups) { + t.Errorf("group count = %d, want %d", len(groups), len(tt.groups)) + } + for i, group := range groups { + if i < len(tt.groups) && group != tt.groups[i] { + t.Errorf("group[%d] = %q, want %q", i, group, tt.groups[i]) + } + } + }) + } +} From 17220e592f7047915f5206c83342c09bf51cbc1a Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Fri, 23 Jan 2026 19:01:27 -0600 Subject: [PATCH 2/8] Finalized TestGroupParens --- parser/requisiteParser_test.go | 65 ++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index dd1b027..c6ce650 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -1,6 +1,8 @@ package parser -import "testing" +import ( + "testing" +) func TestInitMatchers(t *testing.T) { // Test 1: Initialization @@ -45,8 +47,8 @@ func TestGroupParens(t *testing.T) { { name: "Nested parentheses", input: "((A and B) or (C and D))", - expected: "@0", - groups: []string{"@1 or @2", "A and B", "C and D"}, + expected: "@2", + groups: []string{"A and B", "C and D", "@0 or @1"}, }, { name: "Multiple parentheses", @@ -57,14 +59,14 @@ func TestGroupParens(t *testing.T) { { name: "Mismatched closing parentheses", input: "(A and B)) extra text", - expected: "@0 extra text", + expected: "@0) extra text", groups: []string{"A and B"}, }, { name: "Complex expression", input: "MATH 2417 and (PHYS 2125 or (PHYS 2126 and CHEM 1311))", - expected: "MATH 2417 and @0", - groups: []string{"PHYS 2125 or @1", "PHYS 2126 and CHEM 1311"}, + expected: "MATH 2417 and @1", + groups: []string{"PHYS 2126 and CHEM 1311", "PHYS 2125 or @0"}, }, } @@ -85,3 +87,54 @@ func TestGroupParens(t *testing.T) { }) } } + +func TestUngroupText(t *testing.T) { + t.Skip() + // Setup groupList as it would be after parsing + groupList = []string{ + "PHYS 2125 or PHYS 2126", + "A and B", + "C and D", + } + + tests := []struct { + name string + input string + expected string + }{ + { + name: "No group tags", + input: "MATH 2417 and something", + expected: "MATH 2417 and something", + }, + { + name: "Single group tag", + input: "MATH 2417 and @0", + expected: "MATH 2417 and (PHYS 2125 or PHYS 2126)", + }, + { + name: "Multiple group tags", + input: "@1 or @2", + expected: "(A and B) or (C and D)", + }, + { + name: "Nested replacement", + input: "@0 with extra @1", + expected: "(PHYS 2125 or PHYS 2126) with extra (A and B)", + }, + { + name: "Out of bounds tag", + input: "@10", + expected: "@10", // Should remain unchanged + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ungroupText(tt.input) + if result != tt.expected { + t.Errorf("ungroupText() = %q, want %q", result, tt.expected) + } + }) + } +} From 128b4050ce04d4e522da2c9d25d31e6030231834 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Fri, 23 Jan 2026 19:11:33 -0600 Subject: [PATCH 3/8] Finalized TestUngroupText --- parser/requisiteParser_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index c6ce650..308557d 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -89,7 +89,6 @@ func TestGroupParens(t *testing.T) { } func TestUngroupText(t *testing.T) { - t.Skip() // Setup groupList as it would be after parsing groupList = []string{ "PHYS 2125 or PHYS 2126", @@ -124,8 +123,8 @@ func TestUngroupText(t *testing.T) { }, { name: "Out of bounds tag", - input: "@10", - expected: "@10", // Should remain unchanged + input: "@9", + expected: "@9", // Should remain unchanged }, } From 1592d74d8cf67a9f7e60c713fda9f584ba434e91 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sat, 24 Jan 2026 19:20:19 -0600 Subject: [PATCH 4/8] TestAdjacentOthers Finalized --- parser/requisiteParser_test.go | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index 308557d..8fc640b 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -1,7 +1,11 @@ package parser import ( + "reflect" "testing" + + "github.com/UTDNebula/nebula-api/api/schema" + "go.mongodb.org/mongo-driver/bson/primitive" ) func TestInitMatchers(t *testing.T) { @@ -137,3 +141,99 @@ func TestUngroupText(t *testing.T) { }) } } + +func TestJoinAdjacentOthers(t *testing.T) { + testSectionID := primitive.NewObjectID() + + tests := []struct { + name string + input []interface{} + joinString string + expected []interface{} + }{ + { + name: "empty input", + input: []interface{}{}, + joinString: ", ", + expected: []interface{}{}, + }, + { + name: "single other unchanged", + input: []interface{}{ + *schema.NewOtherRequirement("Only one", ""), + }, + joinString: ", ", + expected: []interface{}{ + *schema.NewOtherRequirement("Only one", ""), + }, + }, + { + name: "adjacent others joined", + input: []interface{}{ + *schema.NewOtherRequirement("First", ""), + *schema.NewOtherRequirement("Second", ""), + *schema.NewOtherRequirement("Third", ""), + }, + joinString: " or ", + expected: []interface{}{ + *schema.NewOtherRequirement("First or Second or Third", ""), + }, + }, + { + name: "others at start and end separated by non-other", + input: []interface{}{ + *schema.NewOtherRequirement("Start1", ""), + *schema.NewOtherRequirement("Start2", ""), + schema.NewSectionRequirement(testSectionID), + *schema.NewOtherRequirement("End1", ""), + *schema.NewOtherRequirement("End2", ""), + }, + joinString: " and ", + expected: []interface{}{ + *schema.NewOtherRequirement("Start1 and Start2", ""), + schema.NewSectionRequirement(testSectionID), + *schema.NewOtherRequirement("End1 and End2", ""), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := joinAdjacentOthers(tt.input, tt.joinString) + + if len(result) != len(tt.expected) { + t.Errorf("expected %d items, got %d", len(tt.expected), len(result)) + return // Don't continue if lengths don't match + } + + // Check each item + for i := range result { + switch r := result[i].(type) { + case schema.OtherRequirement: + expected, ok := tt.expected[i].(schema.OtherRequirement) + if !ok { + t.Errorf("position %d: expected OtherRequirement, got %T", i, tt.expected[i]) + continue + } + if r.Description != expected.Description { + t.Errorf("position %d: description mismatch\n got: %q\n want: %q", + i, r.Description, expected.Description) + } + case *schema.SectionRequirement: + expected, ok := tt.expected[i].(*schema.SectionRequirement) + if !ok { + t.Errorf("position %d: expected *SectionRequirement, got %T", i, tt.expected[i]) + continue + } + if r.SectionReference != expected.SectionReference { + t.Errorf("position %d: section reference mismatch", i) + } + default: + if !reflect.DeepEqual(result[i], tt.expected[i]) { + t.Errorf("position %d: item mismatch", i) + } + } + } + }) + } +} From c457f2cba0f8352dc5f8cbc0b688c4d9b8335511 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sat, 24 Jan 2026 22:51:04 -0600 Subject: [PATCH 5/8] TestReqIsThrowaway is finalized --- parser/requisiteParser_test.go | 43 ++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index 8fc640b..9494061 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -237,3 +237,46 @@ func TestJoinAdjacentOthers(t *testing.T) { }) } } + +func TestReqIsThrowaway(t *testing.T) { + tests := []struct { + name string + input interface{} + expected bool + }{ + { + name: "throwaway requirement returns true", + input: schema.Requirement{Type: "throwaway"}, + expected: true, + }, + { + name: "non-throwaway requirement returns false", + input: schema.Requirement{Type: "course"}, + expected: false, + }, + { + name: "not a requirement returns false", + input: "not a requirement", + expected: false, + }, + { + name: "nil returns false", + input: nil, + expected: false, + }, + { + name: "other requirement type returns false", + input: schema.Requirement{Type: "section"}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := reqIsThrowaway(tt.input) + if result != tt.expected { + t.Errorf("reqIsThrowaway(%v) = %v, want %v", tt.input, result, tt.expected) + } + }) + } +} From c6afcbcd9554a62ff97bb78de9d47c30b51cfc8e Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Sun, 25 Jan 2026 15:33:59 -0600 Subject: [PATCH 6/8] Progess on TestMakeSubGroup --- parser/requisiteParser_test.go | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index 9494061..6b5559c 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -2,6 +2,7 @@ package parser import ( "reflect" + "strings" "testing" "github.com/UTDNebula/nebula-api/api/schema" @@ -280,3 +281,90 @@ func TestReqIsThrowaway(t *testing.T) { }) } } + +func TestMakeSubgroup(t *testing.T) { + tests := []struct { + name string + group string + subtext string + requisite interface{} + expectedGroup string + expectedReqLen int + }{ + { + name: "basic text replacement", + group: "Complete MATH 2413", + subtext: "MATH 2413", + requisite: schema.CourseRequirement{ClassReference: "MATH 2413"}, + expectedGroup: "@0", + expectedReqLen: 1, + }, + { + name: "choice requisite replacement", + group: "Credit cannot be received for both courses, @0 and @1", + subtext: "Credit cannot be received for both courses, @0 and @1", + requisite: schema.ChoiceRequirement{}, + expectedGroup: "@2", + expectedReqLen: 3, + }, + { + name: "core requirement replacement", + group: "Completion of an 010 core course", + subtext: "Completion of an 010 core course", + requisite: schema.CourseRequirement{ClassReference: "010", MinimumGrade: "B"}, + expectedGroup: "@0", + expectedReqLen: 1, + }, + { + name: "multiple occurrence replacement", + group: "CS 2336 and CS 2336 lab", + subtext: "CS 2336", + requisite: schema.CourseRequirement{ClassReference: "CS 2336"}, + expectedGroup: "@0 and @0 lab", + expectedReqLen: 1, + }, + { + name: "subtext not found", + group: "Complete CHEM 1311", + subtext: "PHYS 2325", + requisite: schema.CourseRequirement{ClassReference: "PHYS 2325"}, + expectedGroup: "Complete CHEM 1311", + expectedReqLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Reset global variables + requisiteList = []interface{}{} + groupList = []string{} + + // Call the function + result := makeSubgroup(tt.group, tt.subtext, tt.requisite) + + // Check the returned group + if result != tt.expectedGroup { + t.Errorf("expected group %q, got %q", tt.expectedGroup, result) + } + + // Check the requisite list length + if len(requisiteList) != tt.expectedReqLen { + t.Errorf("expected %d requisites, got %d", tt.expectedReqLen, len(requisiteList)) + } + + // Check if requisite was added + if tt.subtext != "" && strings.Contains(tt.group, tt.subtext) && len(requisiteList) > 0 { + if requisiteList[len(requisiteList)-1] != tt.requisite { + t.Errorf("requisite not properly added to list") + } + } + + // Check if group was added to groupList + if len(groupList) != 1 { + t.Errorf("expected 1 group in groupList, got %d", len(groupList)) + } else if groupList[0] != result { + t.Errorf("groupList doesn't contain the result") + } + }) + } +} From 7ff7defbbf9e9dda7487bd4687446e0569976ff4 Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Mon, 26 Jan 2026 01:59:43 -0600 Subject: [PATCH 7/8] Finalized makeSubGroup Test --- parser/requisiteParser_test.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index 6b5559c..f5129d9 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -296,14 +296,25 @@ func TestMakeSubgroup(t *testing.T) { group: "Complete MATH 2413", subtext: "MATH 2413", requisite: schema.CourseRequirement{ClassReference: "MATH 2413"}, - expectedGroup: "@0", + expectedGroup: "Complete @0", expectedReqLen: 1, }, { - name: "choice requisite replacement", - group: "Credit cannot be received for both courses, @0 and @1", - subtext: "Credit cannot be received for both courses, @0 and @1", - requisite: schema.ChoiceRequirement{}, + name: "choice requisite replacement", + group: "Credit cannot be received for both courses, @0 and @1", + subtext: "Credit cannot be received for both courses, @0 and @1", + requisite: schema.ChoiceRequirement{ + Requirement: schema.Requirement{Type: "choice"}, + Choices: &schema.CollectionRequirement{ + Requirement: schema.Requirement{Type: "collection"}, + Name: "", + Required: 1, + Options: []interface{}{ + schema.NewCourseRequirement("CSCI0190", ""), + schema.NewCourseRequirement("CSCI0200", ""), + }, + }, + }, expectedGroup: "@2", expectedReqLen: 3, }, @@ -339,6 +350,14 @@ func TestMakeSubgroup(t *testing.T) { requisiteList = []interface{}{} groupList = []string{} + // For the choice requisite test, pre-populate with 2 dummy requisites + if tt.name == "choice requisite replacement" { + requisiteList = []interface{}{ + schema.CourseRequirement{ClassReference: "DUMMY1", MinimumGrade: ""}, + schema.CourseRequirement{ClassReference: "DUMMY2", MinimumGrade: ""}, + } + } + // Call the function result := makeSubgroup(tt.group, tt.subtext, tt.requisite) From 9c1e87620c143ef8cedd4a4bf59bd1dbbcdf7e1e Mon Sep 17 00:00:00 2001 From: Flavore669 Date: Tue, 27 Jan 2026 01:33:45 -0600 Subject: [PATCH 8/8] Finalized TestFindICN --- parser/requisiteParser_test.go | 101 +++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/parser/requisiteParser_test.go b/parser/requisiteParser_test.go index f5129d9..f80eb55 100644 --- a/parser/requisiteParser_test.go +++ b/parser/requisiteParser_test.go @@ -387,3 +387,104 @@ func TestMakeSubgroup(t *testing.T) { }) } } + +func TestFindICN(t *testing.T) { + // Save original Courses and ensure cleanup, even if there is error + originalCourses := Courses + t.Cleanup(func() { + Courses = originalCourses + }) + + // Create test course + testCourse := &schema.Course{ + Subject_prefix: "CS", + Course_number: "1337", + Internal_course_number: "ICN001", + } + + tests := []struct { + name string + setup map[string]*schema.Course + subject string + number string + wantICN string + wantErr bool + }{ + { + name: "finds existing course", + setup: map[string]*schema.Course{ + "": testCourse, + }, + subject: "CS", + number: "1337", + wantICN: "ICN001", + wantErr: false, + }, + { + name: "course not found returns error", + setup: map[string]*schema.Course{ + "": testCourse, + }, + subject: "MATH", + number: "101", + wantICN: "ERROR", + wantErr: true, + }, + { + name: "empty courses list returns error", + setup: map[string]*schema.Course{}, + subject: "CS", + number: "1337", + wantICN: "ERROR", + wantErr: true, + }, + { + name: "case sensitive match fails", + setup: map[string]*schema.Course{ + "": testCourse, + }, + subject: "cs", // lowercase + number: "1337", + wantICN: "ERROR", + wantErr: true, + }, + { + name: "multiple courses finds correct one", + setup: map[string]*schema.Course{ + "": testCourse, + ".": { + Subject_prefix: "MATH", + Course_number: "101", + Internal_course_number: "ICN002", + }, + }, + subject: "MATH", + number: "101", + wantICN: "ICN002", + wantErr: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Set test data + Courses = tt.setup + + gotICN, err := findICN(tt.subject, tt.number) + + // Check error + if tt.wantErr && err == nil { + t.Error("expected error, got nil") + } + if !tt.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + + // Check ICN + if gotICN != tt.wantICN { + t.Errorf("got ICN %q, want %q", gotICN, tt.wantICN) + } + }) + } + +}