// 功能描述:验证地址坐标、所有权、事务回滚及创建幂等,不访问远程数据。 // 版本:1.0.0。 package user import ( "git.apinb.com/bsm-sdk/core/types" "github.com/DATA-DOG/go-sqlmock" "github.com/gin-gonic/gin" "net/http/httptest" "strings" "testing" ) func TestAddressCoordinates(t *testing.T) { for _, tc := range []struct { lon, lat string valid bool }{ {"", "", true}, {"113.2", "23.4", true}, {"180", "-90", true}, {"181", "0", false}, {"0", "91", false}, {"0", "", false}, {"NaN", "0", false}, {"Inf", "0", false}, } { if validAddressCoordinates(tc.lon, tc.lat) != tc.valid { t.Errorf("坐标边界不正确:%q %q", tc.lon, tc.lat) } } } // addressContext 构造当前用户鉴权,不依赖登录接口。 func addressContext(body, identity string) (*gin.Context, *httptest.ResponseRecorder) { response := httptest.NewRecorder() ctx, _ := gin.CreateTestContext(response) ctx.Set("Auth", &types.JwtClaims{Client: "user_app", Identity: "alice"}) ctx.Request = httptest.NewRequest("POST", "/addresses", strings.NewReader(body)) ctx.Request.Header.Set("Content-Type", "application/json") ctx.Params = gin.Params{{Key: "identity", Value: identity}} return ctx, response } func expectAddressAccount(mock sqlmock.Sqlmock) { mock.ExpectQuery(`SELECT .* FROM "user_account"`).WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "name", "phone"}).AddRow(1, "alice", "测试用户", "13800000001")) } func expectAddressLock(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectQuery(`SELECT "id" FROM "user_account".*FOR UPDATE`).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1)) } func TestAddressMutationsRejectOtherOwner(t *testing.T) { for _, action := range []func(*gin.Context){SetDefaultAddress, DeleteAddress, UpdateAddress} { mock := primaryTestDB(t) expectAddressAccount(mock) expectAddressLock(mock) mock.ExpectQuery(`SELECT .* FROM "user_address".*identity = \$1 AND user_account_id = \$2`).WillReturnRows(sqlmock.NewRows([]string{"id"})) mock.ExpectRollback() ctx, response := addressContext(`{"address":"测试地址"}`, "other-address") action(ctx) if strings.Contains(response.Body.String(), `"code":0`) { t.Fatal("越权修改成功") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } } func TestAddressCreateRetryDoesNotDuplicateOrResetDefault(t *testing.T) { mock := primaryTestDB(t) expectAddressAccount(mock) expectAddressLock(mock) mock.ExpectQuery(`SELECT .* FROM "user_address".*request_no`).WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "status", "address", "contact_name", "contact_phone"}).AddRow(5, "existing", 1, "测试地址", "测试用户", "13800000001")) mock.ExpectCommit() ctx, response := addressContext(`{"address":"测试地址","request_no":"same-request","is_default":true}`, "") SaveAddress(ctx) if !strings.Contains(response.Body.String(), `"identity":"existing"`) { t.Fatal(response.Body.String()) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } } func TestAddressDefaultUpdateFailureRollsBack(t *testing.T) { mock := primaryTestDB(t) expectAddressAccount(mock) expectAddressLock(mock) mock.ExpectQuery(`SELECT .* FROM "user_address"`).WillReturnRows(sqlmock.NewRows([]string{"id", "identity", "status"}).AddRow(5, "owned", 1)) mock.ExpectExec(`UPDATE "user_address" SET "is_default"`).WillReturnResult(sqlmock.NewResult(0, 1)) mock.ExpectExec(`UPDATE "user_address" SET "is_default"`).WillReturnError(sqlmock.ErrCancelled) mock.ExpectRollback() ctx, response := addressContext("", "owned") SetDefaultAddress(ctx) if strings.Contains(response.Body.String(), `"code":0`) { t.Fatal("失败写入被当作成功") } if err := mock.ExpectationsWereMet(); err != nil { t.Fatal(err) } }