-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 { | ||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Note: your solution is also perfectly valid, since the There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package validator | ||
|
||
import ( | ||
"testing" | ||
) | ||
|
||
func TestIsInRange(t *testing.T) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great solution!! |
||
tt := []struct { | ||
name string | ||
num, min, max int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, 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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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)
}
})
}
} There was a problem hiding this comment. Choose a reason for hiding this commentThe 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! |
||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
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!