And why the ChatGPT rate limit can be a good thing
I see problems with the code blocks here. Gonna have to fix that :).
I'm not much of a coder, but I enjoy playing around and learning with it. In writing a website for my youngest daughter, I am purring along, and am writing tests using the Golang standard library.
I write the following test:
// TestDiveUpdate tests the dive update functionality func TestDiveUpdate(t *testing.T) { // Setup LoadEnv() db.Connect() // Sets the TEST_RUN env var to true for views requiring logged in user but tests that don't require a logged in user os.Setenv("TEST_RUN", "true") defer os.Setenv("TEST_RUN", "") // Reset the TEST_RUN env var // Create a router with the test route gin.SetMode(gin.TestMode) router := gin.Default() router.LoadHTMLGlob("../../templates/**/**") router.POST("/admin/dives/:id", controllers.DiveUpdate) // Add the DiveUpdate route router.GET("/admin/dives", controllers.DivesIndex) // Add the DiveIndex route // Insert test data and defer cleanup dg1, dg2, dt1, dt2, bt1, bt2, bh1, bh2 := helpers.CreateTestData() // create a test dive dive, _ := models.DiveCreate("Test Dive", 154, 2.5, uint64(dt1.ID), uint64(dg1.ID), uint64(bt1.ID), uint64(bh1.ID)) // use form to create updated dive data := url.Values{} data.Set("name", "Test Dive Updated") data.Set("number", "155") data.Set("difficulty", "2.6") data.Set("divegroup_id", fmt.Sprintf("%d", dg2.ID)) data.Set("divetype_id", fmt.Sprintf("%d", dt2.ID)) data.Set("boardtype_id", fmt.Sprintf("%d", bt2.ID)) data.Set("boardheight_id", fmt.Sprintf("%d", bh2.ID)) // Create a request and submit it to the router req, err := http.NewRequest(http.MethodPost, "/admin/dives/"+helpers.UintToString(dive.ID), strings.NewReader(data.Encode())) if err != nil { t.Fatalf("Failed to create a new request: %v", err) } req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Create a response recorder w := httptest.NewRecorder() // Submit the request router.ServeHTTP(w, req) // Check the status code if status := w.Code; status != http.StatusFound { t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusFound) } // Check the updated dive updatedDive, _ := models.DiveShow(uint64(dive.ID)) if updatedDive.Name != "Test Dive Updated" { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.Name, "Test Dive Updated") } if updatedDive.Number != 155 { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.Number, 155) } if updatedDive.Difficulty != 2.6 { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.Difficulty, 2.6) } if updatedDive.DiveTypeID != uint64(dt2.ID) { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.DiveTypeID, dt2.ID) } if updatedDive.DiveGroupID != uint64(dg2.ID) { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.DiveGroupID, dg2.ID) } if updatedDive.BoardTypeID != uint64(bt2.ID) { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.BoardTypeID, bt2.ID) } if updatedDive.BoardHeightID != uint64(bh2.ID) { t.Errorf("handler returned unexpected body: got %v want %v", updatedDive.BoardHeightID, bh2.ID) } // Cleanup db.Database.Unscoped().Delete(&updatedDive) // Clean up test data helpers.CleanTestData(dg1, dg2, dt1, dt2, bt1, bt2, bh1, bh2) }
Yes, I am cheating the test by using a TEST_RUN variable to allow these tests to simply run without creating a session each time. Sue me.
Moving beyond that, the test is fine.
It's an update test of one of my models that has four associated models.
But the damn thing doesn't work:
2023/04/28 15:21:46 D:/code/splattastic/models/dive_group.go:24 SLOW SQL >= 200ms [259.904ms] [rows:1] INSERT INTO "dive_groups" ("created_at","updated_at","deleted_at","name") VALUES ('2023-04-28 15:21:46.592','2023-04-28 15:21:46.592',NULL,'Test Dive Group 1') RETURNING "id"2023/04/28 15:21:46 Dive group created: {{189 2023-04-28 15:21:46.5926146 -0500 CDT 2023-04-28 15:21:46.5926146 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Dive Group 1} 2023/04/28 15:21:46 Dive group created: {{190 2023-04-28 15:21:46.7513964 -0500 CDT 2023-04-28 15:21:46.7513964 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Dive Group 2} 2023/04/28 15:21:46 Dive type created: {{203 2023-04-28 15:21:46.8683983 -0500 CDT 2023-04-28 15:21:46.8683983 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Dive Type 1 Q} 2023/04/28 15:21:47 Dive type created: {{204 2023-04-28 15:21:47.0246299 -0500 CDT 2023-04-28 15:21:47.0246299 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Dive Type 2 R} 2023/04/28 15:21:47 Board type created: {{187 2023-04-28 15:21:47.143395 -0500 CDT 2023-04-28 15:21:47.143395 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Board Type 1} 2023/04/28 15:21:47 Board type created: {{188 2023-04-28 15:21:47.305703 -0500 CDT 2023-04-28 15:21:47.305703 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} Test Board Type 2} 2023/04/28 15:21:47 Board height created: {{184 2023-04-28 15:21:47.4239049 -0500 CDT 2023-04-28 15:21:47.4239049 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} 8.5} 2023/04/28 15:21:47 Board height created: {{185 2023-04-28 15:21:47.5980298 -0500 CDT 2023-04-28 15:21:47.5980298 -0500 CDT {0001-01-01 00:00:00 +0000 UTC false}} 6.5} 2023/04/28 15:21:48 D:/code/splattastic/models/dive.go:87 SLOW SQL >= 200ms [378.266ms] [rows:1] SELECT * FROM "dives" WHERE "dives"."id" = 78 AND "dives"."deleted_at" IS NULL ORDER BY "dives"."id" LIMIT 1 2023/04/28 15:21:48 divetypeID: 204 2023/04/28 15:21:48 divegroupID: 190 2023/04/28 15:21:48 boardtypeID: 188 2023/04/28 15:21:48 boardheightID: 185 2023/04/28 15:21:48 D:/code/splattastic/models/dive_type.go:48 record not found [40.500ms] [rows:0] SELECT * FROM "dive_types" WHERE "dive_types"."id" = 190 AND "dive_types"."deleted_at" IS NULL ORDER BY "dive_types"."id" LIMIT 1 2023/04/28 15:21:48 Error getting divetype by id: dive type not found 2023/04/28 15:21:48 Error updating dive: Error getting divetype by id [GIN] 2023/04/28 - 15:21:48 | 500 | 419.1081ms | | POST "/admin/dives/78" Response body: {"error":"Error getting divetype by id"} dive_update_test.go:72: handler returned wrong status code: got 500 want 302 dive_update_test.go:78: handler returned unexpected body: got Test Dive want Test Dive Updated Updated dive: &{Model:{ID:78 CreatedAt:2023-04-28 15:21:48.021533 -0500 CDT UpdatedAt:2023-04-28 15:21:48.021533 -0500 CDT DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Name:Test Dive Number:154 Difficulty:2.5 DiveTypeID:203 DiveType:{Model:{ID:203 CreatedAt:2023-04-28 15:21:46.868398 -0500 CDT UpdatedAt:2023-04-28 15:21:46.868398 -0500 CDT DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Name:Test Dive Type 1 Letter:Q} DiveGroupID:189 DiveGroup:{Model:{ID:189 CreatedAt:2023-04-28 15:21:46.592614 -0500 CDT UpdatedAt:2023-04-28 15:21:46.592614 -0500 CDT DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Name:Test Dive Group 1} BoardTypeID:187 BoardType:{Model:{ID:187 CreatedAt:2023-04-28 15:21:47.143395 -0500 CDT UpdatedAt:2023-04-28 15:21:47.143395 -0500 CDT DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Name:Test Board Type 1} BoardHeightID:184 BoardHeight:{Model:{ID:184 CreatedAt:2023-04-28 15:21:47.423904 -0500 CDT UpdatedAt:2023-04-28 15:21:47.423904 -0500 CDT DeletedAt:{Time:0001-01-01 00:00:00 +0000 UTC Valid:false}} Height:8.5}} --- FAIL: TestDiveUpdate (3.88s) FAIL FAIL github.com/hail2skins/splattastic/controllers/tests 4.718s FAIL
I'm quizzing GPT-4 about it and it is really pretty smart. But here it's meandering about being unhelpful right up until I hit my 25 questions over 3 hours when it went away.
Once it went away I realized something miraculous.
I still have eyes
And they work.
Look closely in that error and you see the test data is created. The error says it can't find "divetype" by the ID of 190. But 190 is the divegroup ID. Why is it looking for the ID of divegroup when divetype is created and sitting there waiting to be used?
Here's my controller:
// DiveUpdate updates a dive func DiveUpdate(c *gin.Context) { idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { log.Printf("Error converting id to uint64: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid id"}) return } name := c.PostForm("name") if name == "" { log.Printf("Error updating dive: name is empty") c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid name"}) return } numberStr := c.PostForm("number") number, err := strconv.Atoi(numberStr) if err != nil { log.Printf("Error converting number to int: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid number"}) return } difficultyStr := c.PostForm("difficulty") difficulty, err := strconv.ParseFloat(difficultyStr, 32) if err != nil { log.Printf("Error converting difficulty to float32: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid difficulty"}) return } diveTypeIDStr := c.PostForm("divetype_id") diveTypeID, err := strconv.ParseUint(diveTypeIDStr, 10, 64) if err != nil { log.Printf("Error converting divetype_id to uint64: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid divetype_id"}) return } diveGroupIDStr := c.PostForm("divegroup_id") diveGroupID, err := strconv.ParseUint(diveGroupIDStr, 10, 64) if err != nil { log.Printf("Error converting dive_group_id to uint64: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid divegroup_id"}) return } boardTypeIDStr := c.PostForm("boardtype_id") boardTypeID, err := strconv.ParseUint(boardTypeIDStr, 10, 64) if err != nil { log.Printf("Error converting board_type_id to uint64: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid boardtype_id"}) return } boardHeightIDStr := c.PostForm("boardheight_id") boardHeightID, err := strconv.ParseUint(boardHeightIDStr, 10, 64) if err != nil { log.Printf("Error converting board_height_id to uint64: %v", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid boardheight_id"}) return } dive, err := models.DiveShow(id) if err != nil { log.Printf("Error fetching dive: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } err = dive.Update(name, number, float32(difficulty), diveGroupID, diveTypeID, boardTypeID, boardHeightID) if err != nil { log.Printf("Error updating dive: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.Redirect(http.StatusFound, "/admin/dives") }
Look really closely here:
err = dive.Update(name, number, float32(difficulty), diveGroupID, diveTypeID, boardTypeID, boardHeightID)
Eureka. Order matters. Change it to this... ```Go err = dive.Update(name, number, float32(difficulty), diveTypeID, diveGroupID, boardTypeID, boardHeightID)
And we're running again.
Why?
// DiveUpdate updates a dive func (dive *Dive) Update(name string, number int, difficulty float32, divetypeID uint64, divegroupID uint64, boardtypeID uint64, boardheightID uint64) error { // Check if the associated records exist _, err := DiveTypeShow(divetypeID) if err != nil { log.Printf("Error getting divetype by id: %v", err) return errors.New("Error getting divetype by id") } _, err = DiveGroupShow(divegroupID) if err != nil { log.Printf("Error getting divegroup by id: %v", err) return errors.New("Error getting divegroup by id") } _, err = BoardTypeShow(boardtypeID) if err != nil { log.Printf("Error getting boardtype by id: %v", err) return errors.New("Error getting boardtype by id") } _, err = BoardHeightShow(boardheightID) if err != nil { log.Printf("Error getting boardheight by id: %v", err) return errors.New("Error getting boardheight by id") } // Update dive fields dive.Name = name dive.Number = number dive.Difficulty = difficulty dive.DiveTypeID = divetypeID dive.DiveGroupID = divegroupID dive.BoardTypeID = boardtypeID dive.BoardHeightID = boardheightID // Save updated dive to the database err = db.Database.Model(&dive).Updates(Dive{ Name: name, Number: number, Difficulty: difficulty, DiveTypeID: divetypeID, DiveGroupID: divegroupID, BoardTypeID: boardtypeID, BoardHeightID: boardheightID, }).Error if err != nil { log.Printf("Error updating dive: %v", err) return errors.New("Error updating dive") } return nil }
The model demands attention :).
This is my first pretty complex model interaction with this language. Intuitively it does make sense that order matters, but I really wouldn't have thought about this at all but for GPT-4 telling me to stop answering questions. I've been using it to quickly get me passed bottlenecks.
But old fashioned human power still has a place.
For now.