From b6d0f3db38cfb35dbc21700fb83dcc7df104a003 Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Mon, 26 Aug 2024 05:41:35 -0700 Subject: [PATCH 01/72] add test for HandleHTTPError undelete --- backend/errors/httperror_test.go | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 backend/errors/httperror_test.go diff --git a/backend/errors/httperror_test.go b/backend/errors/httperror_test.go new file mode 100644 index 00000000..528c9afe --- /dev/null +++ b/backend/errors/httperror_test.go @@ -0,0 +1,63 @@ +package errors + +import ( + "bytes" + "errors" + "log" + "net/http/httptest" + "strings" + "testing" +) + +func TestHandleHTTPError(t *testing.T) { + tests := []struct { + testName string + w *httptest.ResponseRecorder + err error + statusCode int + userMessage string + output string + wrappedMessage string + res bool + }{ + { + testName: "Nil", + w: httptest.NewRecorder(), + err: nil, + statusCode: 200, + userMessage: "", + output: "", + wrappedMessage: "", + res: false, + }, + { + testName: "Server Error 500", + w: httptest.NewRecorder(), + err: errors.New("foo"), + statusCode: 500, + userMessage: "bar", + output: "Error: foo", + wrappedMessage: "{\"error\":\"bar\"}", + res: true, + }, + } + for _, test := range tests { + currentWriter := log.Writer() + var buf bytes.Buffer + log.SetOutput(&buf) + res := HandleHTTPError(test.w, test.err, test.statusCode, test.userMessage) + log.SetOutput(currentWriter) + if test.res != res { + t.Errorf("%s: expected %t, got %t", test.testName, test.res, res) + } + if test.w.Code != test.statusCode { + t.Errorf("%s: expected %d, got %d", test.testName, test.statusCode, test.w.Code) + } + if !strings.Contains(test.w.Body.String(), test.wrappedMessage) { + t.Errorf("%s: expected %s, got %s", test.testName, strings.TrimRight(test.w.Body.String(), "\n"), test.wrappedMessage) + } + if !strings.Contains(buf.String(), test.output) { + t.Errorf("%s: expected Error%s, got %s", test.testName, strings.SplitN(strings.TrimRight(buf.String(), "\n"), "Error", 1)[0], test.output) + } + } +} From d12399500aac6d87db086e7ea77d423df3e08536 Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Mon, 26 Aug 2024 05:53:46 -0700 Subject: [PATCH 02/72] more verbose http codes --- backend/errors/httperror_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/errors/httperror_test.go b/backend/errors/httperror_test.go index 528c9afe..f1315eb5 100644 --- a/backend/errors/httperror_test.go +++ b/backend/errors/httperror_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "log" + "net/http" "net/http/httptest" "strings" "testing" @@ -24,7 +25,7 @@ func TestHandleHTTPError(t *testing.T) { testName: "Nil", w: httptest.NewRecorder(), err: nil, - statusCode: 200, + statusCode: http.StatusOK, userMessage: "", output: "", wrappedMessage: "", @@ -34,7 +35,7 @@ func TestHandleHTTPError(t *testing.T) { testName: "Server Error 500", w: httptest.NewRecorder(), err: errors.New("foo"), - statusCode: 500, + statusCode: http.StatusInternalServerError, userMessage: "bar", output: "Error: foo", wrappedMessage: "{\"error\":\"bar\"}", From d56d930a34be98defb4ea143c3d372c3a694a475 Mon Sep 17 00:00:00 2001 From: Osnat Katz Moon <137817983+astrosnat@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:09:27 +0700 Subject: [PATCH 03/72] Update Dockerfile Now using go 1.23! Tested this on dev, it works --- backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index 9ded26b4..11a886ea 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1.3-labs -FROM golang:1.22.5-bookworm +FROM golang:1.23-bookworm SHELL ["/bin/bash", "-euo", "pipefail", "-c"] From 47cb3458ab73dd389821cbbf89be464965fd8a79 Mon Sep 17 00:00:00 2001 From: Osnat Katz Moon <137817983+astrosnat@users.noreply.github.com> Date: Mon, 26 Aug 2024 23:11:43 +0700 Subject: [PATCH 04/72] Update compose_dev.sh Prints initial settings as defined in setup.yaml. Added for usability reasons - users keep getting confused about account balance and maximum allowable debt --- scripts/dev/compose_dev.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/dev/compose_dev.sh b/scripts/dev/compose_dev.sh index b20aa3ff..dc575d9b 100755 --- a/scripts/dev/compose_dev.sh +++ b/scripts/dev/compose_dev.sh @@ -6,6 +6,8 @@ if [ "$1" = "up" ]; then docker compose --env-file $ENV_PATH --file "$SCRIPT_DIR/scripts/docker-compose-dev.yaml" up -d && \ echo "SocialPredict may be found at http://localhost . This may take a few seconds to load initially." + echo "Here are the initial settings. These can be changed in setup.yaml" + cat $SCRIPT_DIR/backend/setup/setup.yaml elif [ "$1" = "down" ]; then docker compose --env-file $ENV_PATH --file "$SCRIPT_DIR/scripts/docker-compose-dev.yaml" down else From 18e7b490e86493d3f182aecd5da97cbc484295d0 Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Mon, 26 Aug 2024 14:27:46 -0700 Subject: [PATCH 05/72] add failure condition test --- backend/handlers/setup/setuphandler.go | 23 +++++++++++---------- backend/handlers/setup/setuphandler_test.go | 15 +++++++++----- backend/server/server.go | 3 ++- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/backend/handlers/setup/setuphandler.go b/backend/handlers/setup/setuphandler.go index 0ce80f21..03201c81 100644 --- a/backend/handlers/setup/setuphandler.go +++ b/backend/handlers/setup/setuphandler.go @@ -6,17 +6,18 @@ import ( "socialpredict/setup" ) -func GetSetupHandler(w http.ResponseWriter, r *http.Request) { +func GetSetupHandler(loadEconomicsConfig func() (*setup.EconomicConfig, error)) func(w http.ResponseWriter, r *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + appConfig, err := loadEconomicsConfig() + if err != nil { + http.Error(w, "Failed to load economic config", http.StatusInternalServerError) + return + } - appConfig, err := setup.LoadEconomicsConfig() - if err != nil { - http.Error(w, "Failed to load economic config", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(appConfig.Economics) - if err != nil { - http.Error(w, "Failed to encode response", http.StatusInternalServerError) + w.Header().Set("Content-Type", "application/json") + err = json.NewEncoder(w).Encode(appConfig.Economics) + if err != nil { + http.Error(w, "Failed to encode response", http.StatusInternalServerError) + } } } diff --git a/backend/handlers/setup/setuphandler_test.go b/backend/handlers/setup/setuphandler_test.go index d7e872d0..7a3e4828 100644 --- a/backend/handlers/setup/setuphandler_test.go +++ b/backend/handlers/setup/setuphandler_test.go @@ -9,8 +9,6 @@ import ( "testing" ) -var loadEconomicsConfig = setup.LoadEconomicsConfig - func TestGetSetupHandler(t *testing.T) { tests := []struct { Name string @@ -31,14 +29,21 @@ func TestGetSetupHandler(t *testing.T) { "User":{"InitialAccountBalance":0,"MaximumDebtAllowed":500}, "Betting":{"MinimumBet":1,"BetFees":{"InitialBetFee":1,"EachBetFee":0,"SellSharesFee":0}}}`, IsJSONResponse: true, + }, { + Name: "failed to load config", + MockConfigLoader: func() (*setup.EconomicConfig, error) { + return nil, http.ErrBodyNotAllowed + }, + ExpectedStatus: http.StatusInternalServerError, + ExpectedResponse: "Failed to load economic config", + IsJSONResponse: false, }, } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { // Replace the actual loader function with the mock - loadEconomicsConfig = test.MockConfigLoader - defer func() { loadEconomicsConfig = setup.LoadEconomicsConfig }() + loadEconomicsConfig := test.MockConfigLoader req, err := http.NewRequest("GET", "/setup", nil) if err != nil { @@ -46,7 +51,7 @@ func TestGetSetupHandler(t *testing.T) { } rr := httptest.NewRecorder() - handler := http.HandlerFunc(GetSetupHandler) + handler := http.HandlerFunc(GetSetupHandler(loadEconomicsConfig)) handler.ServeHTTP(rr, req) diff --git a/backend/server/server.go b/backend/server/server.go index f70f3a2c..a2228c02 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -11,6 +11,7 @@ import ( setuphandlers "socialpredict/handlers/setup" usershandlers "socialpredict/handlers/users" "socialpredict/middleware" + "socialpredict/setup" "github.com/gorilla/mux" "github.com/rs/cors" @@ -36,7 +37,7 @@ func Start() { router.HandleFunc("/v0/login", middleware.LoginHandler) // application setup information - router.HandleFunc("/v0/setup", setuphandlers.GetSetupHandler).Methods("GET") + router.HandleFunc("/v0/setup", setuphandlers.GetSetupHandler(setup.LoadEconomicsConfig)).Methods("GET") // markets display, market information router.HandleFunc("/v0/markets", marketshandlers.ListMarketsHandler).Methods("GET") router.HandleFunc("/v0/markets/{marketId}", marketshandlers.MarketDetailsHandler).Methods("GET") From 3646abe33bc5329793296f759ac86af9ac41fb4f Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Tue, 27 Aug 2024 23:59:48 -0700 Subject: [PATCH 06/72] formatting and test standards --- backend/errors/httperror_test.go | 43 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/backend/errors/httperror_test.go b/backend/errors/httperror_test.go index f1315eb5..30d0c3c6 100644 --- a/backend/errors/httperror_test.go +++ b/backend/errors/httperror_test.go @@ -12,7 +12,7 @@ import ( func TestHandleHTTPError(t *testing.T) { tests := []struct { - testName string + name string w *httptest.ResponseRecorder err error statusCode int @@ -22,7 +22,7 @@ func TestHandleHTTPError(t *testing.T) { res bool }{ { - testName: "Nil", + name: "Nil", w: httptest.NewRecorder(), err: nil, statusCode: http.StatusOK, @@ -32,7 +32,7 @@ func TestHandleHTTPError(t *testing.T) { res: false, }, { - testName: "Server Error 500", + name: "Server Error 500", w: httptest.NewRecorder(), err: errors.New("foo"), statusCode: http.StatusInternalServerError, @@ -43,22 +43,25 @@ func TestHandleHTTPError(t *testing.T) { }, } for _, test := range tests { - currentWriter := log.Writer() - var buf bytes.Buffer - log.SetOutput(&buf) - res := HandleHTTPError(test.w, test.err, test.statusCode, test.userMessage) - log.SetOutput(currentWriter) - if test.res != res { - t.Errorf("%s: expected %t, got %t", test.testName, test.res, res) - } - if test.w.Code != test.statusCode { - t.Errorf("%s: expected %d, got %d", test.testName, test.statusCode, test.w.Code) - } - if !strings.Contains(test.w.Body.String(), test.wrappedMessage) { - t.Errorf("%s: expected %s, got %s", test.testName, strings.TrimRight(test.w.Body.String(), "\n"), test.wrappedMessage) - } - if !strings.Contains(buf.String(), test.output) { - t.Errorf("%s: expected Error%s, got %s", test.testName, strings.SplitN(strings.TrimRight(buf.String(), "\n"), "Error", 1)[0], test.output) - } + t.Run(test.name, func(t *testing.T) { + currentWriter := log.Writer() + var buf bytes.Buffer + log.SetOutput(&buf) + res := HandleHTTPError(test.w, test.err, test.statusCode, test.userMessage) + log.SetOutput(currentWriter) + if test.res != res { + t.Errorf("got %t, want %t", test.res, res) + } + if test.w.Code != test.statusCode { + t.Errorf("got %d, want %d", test.statusCode, test.w.Code) + } + if !strings.Contains(test.w.Body.String(), test.wrappedMessage) { + t.Errorf("got %s, want %s", strings.TrimRight(test.w.Body.String(), "\n"), test.wrappedMessage) + } + if !strings.Contains(buf.String(), test.output) { + t.Errorf("got Error%s, want %s", strings.SplitN(strings.TrimRight(buf.String(), "\n"), "Error", 1)[0], test.output) + } + }) + } } From 6cbb97c04cbb5297c03598fe55775f2d9dd1768b Mon Sep 17 00:00:00 2001 From: AnhQuan Nguyen Date: Sat, 7 Sep 2024 12:44:52 -0700 Subject: [PATCH 07/72] add frontend file polling for wsl2 --- frontend/package-lock.json | 97 +++++++++++++++++++++ frontend/package.json | 3 +- frontend/src/components/sidebar/Sidebar.jsx | 7 +- frontend/vite.config.mjs | 10 +++ scripts/docker-compose-dev.yaml | 1 - 5 files changed, 112 insertions(+), 6 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 39dd06ee..e7e6c5b4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "react": "^18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.13", "react-router-dom": "^5.3.0", "recharts": "^2.10.3", "web-vitals": "^2.1.4" @@ -26,6 +27,7 @@ "devDependencies": { "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", + "is-wsl": "^3.1.0", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "vite": "^5.1.7" @@ -3005,6 +3007,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3035,6 +3053,25 @@ "node": ">=0.10.0" } }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -3161,6 +3198,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -3947,6 +4000,18 @@ "react": "^18.2.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz", + "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -6959,6 +7024,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6980,6 +7051,15 @@ "is-extglob": "^2.1.1" } }, + "is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "requires": { + "is-docker": "^3.0.0" + } + }, "is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -7058,6 +7138,15 @@ "get-intrinsic": "^1.1.1" } }, + "is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "requires": { + "is-inside-container": "^1.0.0" + } + }, "isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -7590,6 +7679,14 @@ "scheduler": "^0.23.0" } }, + "react-error-boundary": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.13.tgz", + "integrity": "sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==", + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 938d263c..627514dc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -52,8 +52,9 @@ "devDependencies": { "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.17", + "is-wsl": "^3.1.0", "postcss": "^8.4.35", "tailwindcss": "^3.4.1", "vite": "^5.1.7" } -} +} \ No newline at end of file diff --git a/frontend/src/components/sidebar/Sidebar.jsx b/frontend/src/components/sidebar/Sidebar.jsx index 825b4354..f3e7d9f1 100644 --- a/frontend/src/components/sidebar/Sidebar.jsx +++ b/frontend/src/components/sidebar/Sidebar.jsx @@ -127,7 +127,7 @@ const Sidebar = () => { <> {renderCredit()} - + Home @@ -160,9 +160,8 @@ const Sidebar = () => { <>