Skip to content

Done testing exercise and http exercise by Nacchan #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
138 changes: 138 additions & 0 deletions exercise_answers/nacchan/http_exercise.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package main

import (
"bufio"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
)

type UserAttributes struct {
ID int `json:"id"`
Name string `json:"name"`
AccountIds []int `json:"account_ids"`
}

type AccountAttributes struct {
ID int `json:"id"`
UserID int `json:"user_id"`
Name string `json:"name"`
Balance int `json:"balance"`
}

type UserResponse struct {
UserAttributes UserAttributes `json:"attributes"`
}

type AccountResponse struct {
AccountAttributes AccountAttributes `json:"attributes"`
}

const URL = "https://sample-accounts-api.herokuapp.com/users/"

func main() {
var userID int
reader := bufio.NewReader(os.Stdin)
for {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice loop!

There are many ways to read input in Go, including bufio, fmt.Scanf, and others. I am not sure which one is the most performant or most common, but your solution seems robust!

fmt.Println("Enter user ID: ")
userIDString, err := reader.ReadString('\n')
userIDString = strings.TrimSpace(userIDString)
userID, err = strconv.Atoi(userIDString)
if err != nil {
fmt.Println("please enter a valid integer")
continue
}
break
}

user, err := getUser(userID)
if err != nil {
fmt.Println("Error getting user: ", err)
return
}

accounts, err := getAccounts(userID)
if err != nil {
fmt.Println("Error getting accounts: ", err)
return
}

fmt.Println("User: ", user)
fmt.Println("Accounts: ")
printAccounts(accounts)
}

func getUser(id int) (string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great solution!

resp, err := http.Get(URL + strconv.Itoa(id))

if err != nil {
return "", fmt.Errorf("error in making request: %w", err)
}

if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("response error: %w", resp.StatusCode)
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("error in reading response body: %w", err)
}

var res UserResponse
err = json.Unmarshal(body, &res)

if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comes down to personal preferences, so other people may have different suggestions on this. But a more idiomatic way of retrieving an error and checking if it's nil, is like this:

if err = json.Unmarshal(body, &res); err != nil {
	return "", fmt.Errorf("error in parsing json: %v", err)
}

The main benefit of this approach is that the scope of the err variable is limited to the if statement. This style is used whenever the function that you want to check (in this case, json.Unmarshal()) only returns an error object, and you want to immediately check the error.


Note: your solution is also perfectly valid, since the err variable has already been declared in a previous statement. The code that I recommended does not actually limit the scope of the outer err variable, but it's still considered more "go idiomatic", aka it's more common.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assigning variables at the beginning of an if statement certainly gives it a go-like style!(I never saw this style in other language🧐) While I understand that limiting the variable scope isn't always essential, I agree it’s considered good practice and I'll keep that in mind!

return "", fmt.Errorf("error in parsing json: %w", err)
}

return res.UserAttributes.Name, nil
}

func getAccounts(id int) ([]AccountAttributes, error) {
resp, err := http.Get(URL + strconv.Itoa(id) + "/accounts")

if err != nil {
return nil, fmt.Errorf("error in making request: %w", err)
}

if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("response error: %w", resp.StatusCode)
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error in reading response body: %w", err)
}

var res []AccountResponse
err = json.Unmarshal(body, &res)
if err != nil {
return nil, fmt.Errorf("error in parsing json: %w", err)
}

var accounts []AccountAttributes

for _, account := range res {
accounts = append(accounts, account.AccountAttributes)
}

return accounts, nil
}

func printAccounts(accounts []AccountAttributes) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice function!

var totalBalance int = 0

for _, account := range accounts {
fmt.Println(" -", account.Name, ": ", account.Balance)
totalBalance += account.Balance
}
fmt.Println("Total balance: ", totalBalance)
}
17 changes: 17 additions & 0 deletions exercise_answers/nacchan/validator/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package validator

// IsInRange checks if a number is within the specified range (inclusive)
func IsInRange(num, min, max int) bool {
// Check if the number is less than the minimum
if num < min {
return false
}

// Check if the number is greater than the maximum
if num > max {
return false
}

// If we get here, the number is within range
return true
}
65 changes: 65 additions & 0 deletions exercise_answers/nacchan/validator/validator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package validator

import (
"testing"
)

func TestIsInRange(t *testing.T) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great solution!!

tt := []struct {
name string
num, min, max int
Copy link
Collaborator

@mavrikis-kostas mavrikis-kostas Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your approach is common to separate the input and output parameters of the test! In this case, num, min, max are all input parameters, so they are placed in the same line 👏

Another way to separate them, if we have a lot of input parameters (maybe 4+), is using an extra struct. Check the IntelliJ-generated code that I pasted in the below comment.

expected bool
}{
{
name: "within range",
num: 5,
min: 1,
max: 10,
expected: true,
},
{
name: "below range",
num: 0,
min: 1,
max: 10,
expected: false,
},
{
name: "above range",
num: 11,
min: 1,
max: 10,
expected: false,
},
{
name: "equal to min",
num: 1,
min: 1,
max: 10,
expected: true,
},
{
name: "equal to max",
num: 10,
min: 1,
max: 10,
expected: true,
},
{
name: "zero",
num: 0,
min: 0,
max: 0,
expected: true,
},
}

for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
result := IsInRange(tc.num, tc.min, tc.max)
if result != tc.expected {
t.Errorf("expected %v, got %v", tc.expected, result)
Copy link
Collaborator

@mavrikis-kostas mavrikis-kostas Apr 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message is clear, and shows what went wrong in the test!

But some small optional adjustments are:

  • Instead of expected x, got y, Go usually follows the opposite order got x, want y. This is also explained briefly in the "Useful Test Failures" section of the Go Wiki.
  • Mentioning the function name is also a common pattern, so in this case the error would be:
    • t.Errorf("IsInRange() = %v, want %v", result, tt.expected)

Note: in IAA we will probably use the assert package of the testify library, so the error messages will automatically be handled by the library.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this is the auto-generated test from IntelliJ, that follows the general Go conventions:

func TestIsInRange(t *testing.T) {
	type args struct {
		num int
		min int
		max int
	}
	tests := []struct {
		name string
		args args
		want bool
	}{
		// TODO: Add test cases.
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := IsInRange(tt.args.num, tt.args.min, tt.args.max); got != tt.want {
				t.Errorf("IsInRange() = %v, want %v", got, tt.want)
			}
		})
	}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see—it's a Go convention to include the test's objective in the function name and clearly distinguish between got and want. Thanks for pointing out this helpful reference!

}
})
}
}