99 lines
3.7 KiB
Go
99 lines
3.7 KiB
Go
// 功能描述:验证地址坐标、所有权、事务回滚及创建幂等,不访问远程数据。
|
||
// 版本: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)
|
||
}
|
||
}
|