Skip to content

Commit

Permalink
test(unit): Add tests for GetProfile()
Browse files Browse the repository at this point in the history
- Unit tests for GetProfile
- WIP error handling improvements
  • Loading branch information
ditsuke committed Dec 28, 2022
1 parent 28237dd commit a8abf20
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 6 deletions.
84 changes: 78 additions & 6 deletions amizone/amizone_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ func TestAmizoneClient_GetAttendance(t *testing.T) {
nonLoggedInClient := getNonLoggedInClient(g)
loggedInClient := getLoggedInClient(g)

gock.Clean()

testCases := []struct {
name string
amizoneClient *amizone.Client
Expand Down Expand Up @@ -99,7 +101,7 @@ func TestAmizoneClient_GetAttendance(t *testing.T) {
for _, c := range testCases {
t.Run(c.name, func(t *testing.T) {
g := NewGomegaWithT(t)
defer gock.Off()
t.Cleanup(setupNetworking)

c.setup(g)

Expand Down Expand Up @@ -159,7 +161,7 @@ func TestClient_GetSemesters(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
g := NewWithT(t)
t.Cleanup(gock.Off)
t.Cleanup(setupNetworking)
testCase.setup(g)

semesters, err := testCase.client.GetSemesters()
Expand Down Expand Up @@ -313,7 +315,7 @@ func TestClient_GetCurrentCourses(t *testing.T) {
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
g := NewWithT(t)
t.Cleanup(gock.Off)
t.Cleanup(setupNetworking)
testCase.setup(g)

courses, err := testCase.client.GetCurrentCourses()
Expand All @@ -323,18 +325,89 @@ func TestClient_GetCurrentCourses(t *testing.T) {
}
}

func TestClient_GetProfile(t *testing.T) {
g := NewWithT(t)

setupNetworking()
t.Cleanup(teardown)

loggedInClient := getLoggedInClient(g)
nonLoggedInClient := getNonLoggedInClient(g)

testCases := []struct {
name string
client *amizone.Client
setup func(g *WithT)
profileMatcher func(g *WithT, profile *amizone.Profile)
errMatcher func(g *WithT, err error)
}{
{
name: "amizone client logged in and returns the (mock) profile page",
client: loggedInClient,
setup: func(g *WithT) {
err := mock.GockRegisterProfilePage()
g.Expect(err).ToNot(HaveOccurred())
},
profileMatcher: func(g *WithT, profile *amizone.Profile) {
g.Expect(profile).To(Equal(&amizone.Profile{
Name: mock.StudentName,
EnrollmentNumber: mock.StudentEnrollmentNumber,
EnrollmentValidity: mock.StudentIDValidity.Time(),
DateOfBirth: mock.StudentDOB.Time(),
Batch: mock.StudentBatch,
Program: mock.StudentProgram,
BloodGroup: mock.StudentBloodGroup,
IDCardNumber: mock.StudentIDCardNumber,
UUID: mock.StudentUUID,
}))
},
errMatcher: func(g *WithT, err error) {
g.Expect(err).ToNot(HaveOccurred())
},
},
{
name: "amizone client is not logged in and returns the login page",
client: nonLoggedInClient,
setup: func(g *WithT) {
_ = mock.GockRegisterUnauthenticatedGet("/IDCard")
},
profileMatcher: func(g *WithT, profile *amizone.Profile) {
g.Expect(profile).To(BeNil())
},
errMatcher: func(g *WithT, err error) {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring("not logged in"))
},
},
}

for _, testCases := range testCases {
t.Run(testCases.name, func(t *testing.T) {
g := NewWithT(t)
t.Cleanup(setupNetworking)
testCases.setup(g)

profile, err := testCases.client.GetProfile()
testCases.errMatcher(g, err)
testCases.profileMatcher(g, profile)
})
}
}

// Test utilities

// setupNetworking uses gock to disable real HTTP networking to ensure we don't use any real network calls
// for unit tests.
// setupNetworking tears down any existing network mocks and sets up gock anew to intercept network
// calls and disable real network calls.
func setupNetworking() {
// tear everything all routes down
teardown()
gock.Intercept()
gock.DisableNetworking()
}

// teardown disables all networking restrictions and mock routes registered with gock for unit testing.
func teardown() {
gock.Clean()
gock.Off()
gock.EnableNetworking()
}
Expand All @@ -346,7 +419,6 @@ func getNonLoggedInClient(g *GomegaWithT) *amizone.Client {
}

func getLoggedInClient(g *GomegaWithT) *amizone.Client {
defer gock.Off()
err := mock.GockRegisterLoginPage()
g.Expect(err).ToNot(HaveOccurred(), "failed to register mock login page")
err = mock.GockRegisterLoginRequest()
Expand Down
18 changes: 18 additions & 0 deletions amizone/internal/mock/constants.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package mock

import "time"

type Timestamp int64

func (t Timestamp) Time() time.Time {
return time.Unix(int64(t), 0).UTC()
}

// Constants for use in tests using the mock package to create Gock requests, etc.
const (
ValidUser = "fakeUsername"
Expand All @@ -14,4 +22,14 @@ const (

// StudentUUID is the UUID associated with the student used across testdata in filesystem.
StudentUUID = "98RFGK88-A01C-1JJO-N73D-4BJR42B33J51"

StudentName = "John Doe"
StudentEnrollmentNumber = "A2305221007"
StudentIDCardNumber = "95188911"
StudentBloodGroup = "B-ve"
StudentProgram = "B.Tech (CSE)"
StudentBatch = "2020-2024"

StudentIDValidity Timestamp = 1719705600
StudentDOB Timestamp = 986428800
)
9 changes: 9 additions & 0 deletions amizone/internal/mock/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ func GockRegisterCurrentCoursesPage() error {
return nil
}

func GockRegisterProfilePage() error {
mockProfile, err := IDCardPage.Open()
if err != nil {
return errors.New("failed to open mock profile page: " + err.Error())
}
GockRegisterAuthenticatedGet("/IDCard", mockProfile)
return nil
}

func GockRegisterSemWiseCoursesPage() error {
mockCourses, err := CoursesPageSemWise.Open()
if err != nil {
Expand Down
11 changes: 11 additions & 0 deletions amizone/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import (
"k8s.io/klog/v2"
)

const (
ErrNon200StatusCode = "received non-200 status code from amizone - is it down?"
)

// doRequest is an internal http request helper to simplify making requests.
// This method takes care of both composing requests, setting custom headers and such as needed.
// If tryLogin is true, the Client will attempt to log in if it is not already logged in.
Expand Down Expand Up @@ -40,12 +44,19 @@ func (a *Client) doRequest(tryLogin bool, method string, endpoint string, body i
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

// TODO: check error handling logic following here
response, err := a.client.Do(req)
if err != nil {
klog.Errorf("Failed to visit endpoint '%s': %s", endpoint, err)
return nil, fmt.Errorf("%s: %w", ErrFailedToVisitPage, err)
}

// Amizone uses code 200 even for POST requests, so we make sure we have that before proceeding.
if response.StatusCode != http.StatusOK {
klog.Warningf("Received non-200 status code from endpoint '%s': %d. Amizone down?", endpoint, response.StatusCode)
return nil, fmt.Errorf("%s: %d", ErrNon200StatusCode, response.StatusCode)
}

// Read the response into a byte array, so we can reuse it.
responseBody, err := io.ReadAll(response.Body)
if err != nil {
Expand Down

0 comments on commit a8abf20

Please sign in to comment.