package onboarding import ( "strings" "testing" "github.com/google/uuid" ) // A verified new phone must enter the family-creation flow without a prior // customer account, household membership, or device binding. func TestWeChatNewPhoneCreatesCustomerWithoutFamilyOrDevice(t *testing.T) { f := newLoginFixture(t) provider := &fakeWeChat{openID: uuid.NewString(), phone: f.phone} f.s.options.WeChat = provider f.s.options.WeChatAppID = "wx4553119e05f11660" phoneHash := domainMAC(f.s.crypt.hashKey, "PHONE1\n"+f.phone) start := func() string { status, data := miniRequest(t, f.s, "POST", "/api/v1/mini/login/session", "", map[string]string{"portal": "customer"}) if status != 200 { t.Fatalf("session status %d", status) } return "MiniLogin " + data["login_token"].(string) } auth := start() status, data := miniRequest(t, f.s, "POST", "/api/v1/mini/login/wechat", auth, WeChatLoginInput{LoginCode: "before-phone"}) if status != 200 || data["phone_required"] != true { t.Fatal("new identity must request phone authorization") } var count int must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM customer_phone_credential WHERE phone_digest=$1`, phoneHash).Scan(&count)) if count != 0 { t.Fatal("created phone before authorization") } status, data = miniRequest(t, f.s, "POST", "/api/v1/mini/login/wechat", auth, WeChatLoginInput{LoginCode: "authorized", PhoneCode: "verified-phone-code"}) if status != 200 || data["access_token"] == nil { t.Fatalf("verified new phone was not registered: %d", status) } token := strings.TrimPrefix(data["access_token"].(string), "customer.") principal, err := f.s.AccountPrincipal(f.ctx, token, "", false) must(t, err) var uid, kind, tenant string must(t, f.s.pool.QueryRow(f.ctx, `SELECT id::text,account_kind,tenant_id::text FROM user_account WHERE oidc_issuer=$1 AND oidc_subject=$2 AND status='active'`, principal.Issuer, principal.Subject).Scan(&uid, &kind, &tenant)) if kind != "customer" || tenant != f.tenant { t.Fatal("wrong signup account kind or tenant") } must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM household_member WHERE user_id=$1`, uid).Scan(&count)) if count != 0 { t.Fatal("signup must not create or join a household") } must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM device_binding WHERE bound_by=$1`, uid).Scan(&count)) if count != 0 { t.Fatal("signup must not require device binding") } status, data = miniRequest(t, f.s, "POST", "/api/v1/mini/login/wechat", start(), WeChatLoginInput{LoginCode: "repeat-login"}) if status != 200 || data["access_token"] == nil { t.Fatal("repeat login failed") } again, err := f.s.AccountPrincipal(f.ctx, strings.TrimPrefix(data["access_token"].(string), "customer."), "", false) must(t, err) if again.Subject != principal.Subject || again.Issuer != principal.Issuer { t.Fatal("repeat login changed account") } must(t, f.s.pool.QueryRow(f.ctx, `SELECT count(*) FROM user_account WHERE oidc_issuer=$1 AND oidc_subject=$2`, principal.Issuer, principal.Subject).Scan(&count)) if count != 1 { t.Fatal("repeat login duplicated account") } }