-
Notifications
You must be signed in to change notification settings - Fork 0
회사들 리스트 조회 api 추가 & 테스트 db config설정 #13
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
Changes from all commits
501aa5f
8ff00ce
cdb2521
1e7d0ec
662782d
32ac73b
b5f3161
b86a8e5
a7f0999
1bacbee
8e196a6
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 |
|---|---|---|
| @@ -1,43 +1,49 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "log" | ||
|
|
||
| "github.com/joho/godotenv" | ||
| "github.com/techbloghub/server/config" | ||
| "github.com/techbloghub/server/ent" | ||
| _ "github.com/techbloghub/server/ent/runtime" | ||
| "github.com/techbloghub/server/internal/database" | ||
| "github.com/techbloghub/server/internal/http/router" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
| _ "github.com/lib/pq" | ||
| ) | ||
|
|
||
| func main() { | ||
| cfg, cfgErr := config.NewConfig() | ||
| if cfgErr != nil { | ||
| log.Fatalf("failed to load config: %v", cfgErr) | ||
| errEnv := godotenv.Load(".env") | ||
| if errEnv != nil { | ||
| log.Print("failed to reading .env", errEnv) | ||
| } | ||
|
|
||
| // DB 연결 | ||
| client, errPg := database.ConnectDatabase(cfg) | ||
| if errPg != nil { | ||
| log.Fatalf("failed to connect database: %v", errPg) | ||
| cfg, err := config.NewConfig() | ||
| if err != nil { | ||
| log.Fatalf("failed to load config: %v", err) | ||
| } | ||
| defer client.Close() | ||
|
|
||
| // 서버 실행 | ||
| r := setRouter() | ||
| routerErr := r.Run(":" + cfg.ServerConfig.Port) | ||
| if routerErr != nil { | ||
| fmt.Println("Error while running server: ", cfgErr) | ||
| return | ||
|
|
||
| r, dbClient, err := createServer(cfg) | ||
| if err != nil { | ||
| log.Fatalf("failed to create server: %v", err) | ||
| } | ||
| defer dbClient.Close() | ||
|
|
||
| if err := r.Run(":" + cfg.ServerConfig.Port); err != nil { | ||
| log.Fatalf("Error while running server: %v", err) | ||
| } | ||
| } | ||
|
|
||
| func setRouter() *gin.Engine { | ||
| func createServer(cfg *config.Config) (*gin.Engine, *ent.Client, error) { | ||
| client, err := database.ConnectDatabase(cfg) | ||
| if err != nil { | ||
| return nil, nil, err | ||
|
Member
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. 리턴 여러개는 더 익숙해저야겠다ㅋㅋㅋㅋㅋㅋㅋ |
||
| } | ||
|
|
||
| r := gin.Default() | ||
| r.GET("/ping", func(context *gin.Context) { | ||
| context.String(200, "pong") | ||
| }) | ||
| return r | ||
| router.InitRouter(r, client) | ||
|
|
||
| return r, client, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,33 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "github.com/techbloghub/server/internal/testutils" | ||
| ) | ||
|
|
||
| func TestPingEndpoint(t *testing.T) { | ||
| // Set Gin to Test Mode | ||
| gin.SetMode(gin.TestMode) | ||
| func TestMainIntegration(t *testing.T) { | ||
| cfg, err := testutils.NewTestConfig(t) | ||
|
|
||
| // Create a new router instance using the private setRouter function | ||
| router := setRouter() | ||
| require.NoError(t, err, "config 로드 실패") | ||
|
|
||
| // Create a test HTTP recorder | ||
| w := httptest.NewRecorder() | ||
| r, client, err := createServer(cfg) | ||
| require.NoError(t, err, "서버 생성중 에러 발생") | ||
| require.NotNil(t, r, "gin server생성 실패") | ||
| require.NotNil(t, client, "db client 생성 실패") | ||
|
|
||
| // Create a test request to the /ping endpoint | ||
| req, err := http.NewRequest("GET", "/ping", nil) | ||
| assert.NoError(t, err) | ||
| defer client.Close() | ||
|
|
||
| // Serve the request using the router | ||
| router.ServeHTTP(w, req) | ||
| ts := httptest.NewServer(r) | ||
| defer ts.Close() | ||
|
|
||
| // Assert the response code is 200 | ||
| assert.Equal(t, http.StatusOK, w.Code) | ||
|
|
||
| // Assert the response body is "pong" | ||
| assert.Equal(t, "pong", w.Body.String()) | ||
| // Make a request to /ping to ensure everything is wired up | ||
| url := fmt.Sprintf("%s/ping", ts.URL) | ||
| resp, err := http.Get(url) | ||
| require.NoError(t, err, "/ping 호출중 에러 발생") | ||
| require.Equal(t, http.StatusOK, resp.StatusCode, "200 OK반환 실패") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "github.com/gin-gonic/gin" | ||
| "github.com/techbloghub/server/ent" | ||
| ) | ||
|
|
||
| // Company 정보 Response 구조체 | ||
| type CompanyResponse struct { | ||
| ID int `json:"id"` | ||
| Name string `json:"name"` | ||
| LogoURL string `json:"logo_url"` | ||
| BlogURL string `json:"blog_url"` | ||
| } | ||
|
|
||
| type CompanyListResponse struct { | ||
| Companies []CompanyResponse `json:"companies"` | ||
| } | ||
|
|
||
| func ListCompanies(client *ent.Client) gin.HandlerFunc { | ||
| return func(c *gin.Context) { | ||
| entCompanies, err := client.Company.Query().All(c) | ||
| if err != nil { | ||
| c.JSON(500, gin.H{"error": err}) | ||
| return | ||
| } | ||
|
|
||
| companies := make([]CompanyResponse, len(entCompanies)) | ||
| for i, company := range entCompanies { | ||
| companies[i] = CompanyResponse{ | ||
| ID: company.ID, | ||
| Name: company.Name, | ||
| LogoURL: company.LogoURL.String(), | ||
| BlogURL: company.BlogURL.String(), | ||
| } | ||
| } | ||
|
|
||
| c.JSON(200, CompanyListResponse{ | ||
| Companies: companies, | ||
| }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| package handler_test | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "net/url" | ||
| "testing" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/techbloghub/server/ent" | ||
| "github.com/techbloghub/server/internal/http/handler" | ||
| "github.com/techbloghub/server/internal/testutils" | ||
| ) | ||
|
|
||
| func TestListCompanies(t *testing.T) { | ||
| testutils.TransactionalTest(t, func(t *testing.T, client *ent.Client) { | ||
|
Member
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. testUtils.TransactionalTest 좋은데요?
Member
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. 음 문제될법한 케이스나 좀 더 고민을 해볼 필요는 있을 수 있지만,,, 일단 간단하게 먼저 만들어봤어용 나중에 트랜잭션 관리할때도 withTrnasaction등으로 관리할 수 있는 함수 만들던가 해볼께요. |
||
| // seedn data 추가 | ||
| seedCompanies(t, client) | ||
|
|
||
| // Create a new Gin router for test environment | ||
| gin.SetMode(gin.TestMode) | ||
| r := gin.New() | ||
| r.GET("/companies", handler.ListCompanies(client)) | ||
|
|
||
| // Create a new HTTP request | ||
| req, _ := http.NewRequest("GET", "/companies", nil) | ||
|
|
||
| // Create a response recorder | ||
| w := httptest.NewRecorder() | ||
|
|
||
| // Perform the request | ||
| r.ServeHTTP(w, req) | ||
|
|
||
| // Check the status code | ||
| assert.Equal(t, http.StatusOK, w.Code) | ||
|
|
||
| // Check the response body | ||
| var actualResponse handler.CompanyListResponse | ||
| err := json.Unmarshal(w.Body.Bytes(), &actualResponse) | ||
| if err != nil { | ||
| t.Fatalf("failed to unmarshal response: %v", err) | ||
| } | ||
|
|
||
| expectedResponse := []map[string]interface{}{ | ||
| { | ||
| "name": "Company A", | ||
| "logo_url": "http://example.com/logoA.png", | ||
| "blog_url": "http://example.com/blogA", | ||
| }, | ||
| { | ||
| "name": "Company B", | ||
| "logo_url": "http://example.com/logoB.png", | ||
| "blog_url": "http://example.com/blogB", | ||
| }, | ||
| } | ||
|
|
||
| for i, company := range actualResponse.Companies { | ||
| assert.Equal(t, expectedResponse[i]["name"], company.Name) | ||
| assert.Equal(t, expectedResponse[i]["logo_url"], company.LogoURL) | ||
| assert.Equal(t, expectedResponse[i]["blog_url"], company.BlogURL) | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| func seedCompanies(t *testing.T, client *ent.Client) { | ||
| _, err := client.Company.CreateBulk( | ||
| client.Company.Create(). | ||
| SetName("Company A"). | ||
| SetLogoURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/logoA.png"}). | ||
| SetBlogURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/blogA"}). | ||
| SetRssURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/rssA"}), | ||
| client.Company.Create(). | ||
| SetName("Company B"). | ||
| SetLogoURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/logoB.png"}). | ||
| SetBlogURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/blogB"}). | ||
| SetRssURL(&url.URL{Scheme: "http", Host: "example.com", Path: "/rssB"}), | ||
| ).Save(context.Background()) | ||
| if err != nil { | ||
| t.Fatalf("failed to seed companies: %v", err) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package handler | ||
|
|
||
| import "github.com/gin-gonic/gin" | ||
|
|
||
| func PingPong(context *gin.Context) { | ||
| context.String(200, "pong\n") | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package router | ||
|
|
||
| import ( | ||
| "github.com/gin-gonic/gin" | ||
| "github.com/techbloghub/server/ent" | ||
| "github.com/techbloghub/server/internal/http/handler" | ||
| ) | ||
|
|
||
| func InitRouter(r *gin.Engine, client *ent.Client) { | ||
| // PingPong 테스트 | ||
| r.GET("/ping", handler.PingPong) | ||
|
|
||
| // 회사 리스트 조회 | ||
| // curl -X GET http://localhost:8080/companies | ||
| r.GET("/companies", handler.ListCompanies(client)) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package testutils | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/techbloghub/server/config" | ||
| ) | ||
|
|
||
| func NewTestConfig(t *testing.T) (*config.Config, error) { | ||
| t.Setenv("ENV", "test") | ||
| t.Setenv("PORT", "8081") | ||
| t.Setenv("POSTGRES_HOST", "localhost") | ||
| t.Setenv("POSTGRES_USER", "example-user") | ||
| t.Setenv("POSTGRES_PASSWORD", "password") | ||
| t.Setenv("POSTGRES_DB", "tbh-db") | ||
| t.Setenv("POSTGRES_PORT", "5433") | ||
|
|
||
| return config.NewConfig() | ||
| } | ||
|
Comment on lines
+9
to
+19
Member
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. 여기가 언급해주신 부분이구만요!
Member
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. 넹 일단 테스트용 그냥 하드코딩 하고,, t.Setenv가 테스트 종료후에 원래값으로 바꿔주는것도 있네용 |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package testutils | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "testing" | ||
|
|
||
| _ "github.com/lib/pq" | ||
| "github.com/techbloghub/server/ent" | ||
| "github.com/techbloghub/server/ent/enttest" | ||
| ) | ||
|
|
||
| func SetupDB(t *testing.T) (*ent.Client, *ent.Tx) { | ||
| // test env 설정 | ||
|
|
||
| cfg, err := NewTestConfig(t) | ||
| if err != nil { | ||
| t.Fatalf("config 로딩 실패: %v", err) | ||
| } | ||
| pgCfg := cfg.PostgresConfig | ||
|
|
||
| // enttest: client 생성 & migration 실행 | ||
| client := enttest.Open(t, "postgres", fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable", | ||
| pgCfg.Host, pgCfg.Port, pgCfg.User, pgCfg.Db, pgCfg.Password)) | ||
|
|
||
| // transaction 시작 | ||
| tx, err := client.Tx(context.Background()) | ||
| if err != nil { | ||
| t.Fatalf("트랜잭션 시작 실패: %v", err) | ||
| } | ||
|
|
||
| return client, tx | ||
| } | ||
|
|
||
| func TearDown(client *ent.Client, tx *ent.Tx) { | ||
| tx.Rollback() | ||
| client.Close() | ||
| } | ||
|
|
||
| /** | ||
| * TransactionalTest는 트랜잭션을 사용하여 테스트를 실행하는 함수입니다. | ||
| * 테스트가 종료된 후 트랜잭션을 롤백하여 데이터베이스 상태를 원래대로 복원합니다. | ||
| * | ||
| * @param t *testing.T - 테스트 핸들러 | ||
| * @param fn func(t *testing.T, client *ent.Client) - 테스트 함수, ent.Client를 인자로 받습니다. | ||
| */ | ||
| func TransactionalTest(t *testing.T, fn func(t *testing.T, client *ent.Client)) { | ||
| client, tx := SetupDB(t) | ||
| defer func() { | ||
| if err := tx.Rollback(); err != nil { | ||
| t.Fatalf("트랜잭션 롤백 실패: %v", err) | ||
| } | ||
| client.Close() | ||
| }() | ||
|
|
||
| fn(t, tx.Client()) | ||
| } |
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.
[질문]
go/gin에서는 기본적으로 깃허브 레포 통해서 패키지를 관리하나요?! 아니면 따로 설정을 해둔건가요?
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.
server/go.mod
Lines 1 to 4 in 77db124
요기 보면 처음에 프로젝트 초기화할때 패키지 모듈명을 이런식으로 원격저장소 주소로 해서 초기화를 보통 합니당.
공식문서에 적혀있는 내용
링크
요거 물어본거가 맞는거겠죠..?ㅎㅎㅎ
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.
오 맞습니다 ㅎㅎㅎㅎ