-
Notifications
You must be signed in to change notification settings - Fork 0
Done testing exercise and http exercise by Nacchan #2
base: main
Are you sure you want to change the base?
Changes from 2 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 { | ||
|
Collaborator
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 loop! There are many ways to read input in Go, including |
||
| 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) { | ||
|
Collaborator
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: %v", err) | ||
|
||
| } | ||
|
|
||
| if resp.StatusCode != http.StatusOK { | ||
| return "", fmt.Errorf("response error: %v", resp.StatusCode) | ||
| } | ||
|
|
||
| defer resp.Body.Close() | ||
|
|
||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return "", fmt.Errorf("error in reading response body: %v", err) | ||
| } | ||
|
|
||
| var res UserResponse | ||
| err = json.Unmarshal(body, &res) | ||
|
|
||
| if err != nil { | ||
|
Collaborator
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
Collaborator
Author
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: %v", 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: %v", err) | ||
| } | ||
|
|
||
| if resp.StatusCode != http.StatusOK { | ||
| return nil, fmt.Errorf("response error: %v", resp.StatusCode) | ||
| } | ||
|
|
||
| defer resp.Body.Close() | ||
|
|
||
| body, err := io.ReadAll(resp.Body) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error in reading response body: %v", err) | ||
| } | ||
|
|
||
| var res []AccountResponse | ||
| err = json.Unmarshal(body, &res) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error in parsing json: %v", err) | ||
| } | ||
|
|
||
| var accounts []AccountAttributes = []AccountAttributes{} | ||
|
||
|
|
||
| for _, account := range res { | ||
| accounts = append(accounts, account.AccountAttributes) | ||
| } | ||
|
|
||
| return accounts, nil | ||
| } | ||
|
|
||
| func printAccounts(accounts []AccountAttributes) { | ||
|
Collaborator
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) { | ||
|
Collaborator
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 | ||
|
Collaborator
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) | ||
|
Collaborator
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.
Collaborator
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)
}
})
}
}
Collaborator
Author
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.
nitpick about naming; in Go we need to use consistent case (all small, or all capitalized) for initialisms and acronyms. For example,
ID,AccountIDs,id(for unexported variable),userIDetc.You can see Go Wiki and the Google Style Guide for more examples!
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.
Google style guide looks very easy to understand! Thanks👏