Unverified Commit e491e063 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

fix(api): 消费 op-login 请求时要求已审批,fake 同步

parent 009e81b4
Loading
Loading
Loading
Loading
+5 −5
Changes for internal/api/api_test.go: 5 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -1734,13 +1734,13 @@ func (f *fakeRepo) OpLoginRequestByID(_ context.Context, id string) (*OpLoginReq
	}, nil
}

// ConsumeOpLoginRequest stamps consumed on an unconsumed, unexpired request (the
// finish path's single-use guard), mirroring the PG zero-rows-else UPDATE. The
// approval gate is read by the handler BEFORE this call, so consume only checks
// consumed_at and expiry (exactly as PG does).
// ConsumeOpLoginRequest stamps consumed on an approved, unconsumed, unexpired
// request (the finish path's single-use guard), mirroring the PG zero-rows-else
// UPDATE. The handler reads the approval first too; the store refuses a pending
// request on its own so the single-use guard never depends on that read.
func (f *fakeRepo) ConsumeOpLoginRequest(_ context.Context, id string, now time.Time) error {
	r, ok := f.opLogins[id]
	if !ok || r.consumed || !r.expiresAt.After(now) {
	if !ok || r.status != "approved" || r.consumed || !r.expiresAt.After(now) {
		return ErrNotFound
	}
	r.consumed = true
+1 −1
Changes for internal/api/pgrepo.go: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -2948,7 +2948,7 @@ func (p *PGRepo) ApproveOpLogin(ctx context.Context, id, approverUserID string,
func (p *PGRepo) ConsumeOpLoginRequest(ctx context.Context, id string, now time.Time) error {
	res, err := p.db.ExecContext(ctx,
		`UPDATE op_login_requests SET consumed_at = $2
		 WHERE id = $1 AND consumed_at IS NULL AND expires_at > $2`,
		 WHERE id = $1 AND approved_at IS NOT NULL AND consumed_at IS NULL AND expires_at > $2`,
		id, now)
	if err != nil {
		return err
+9 −0
Changes for internal/pgint/pgint_test.go: 9 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -1260,6 +1260,15 @@ func TestOpLoginStateMachine(t *testing.T) {
		t.Fatal("fresh pending request missing from the list")
	}

	// A pending request is not a ticket: the store refuses to consume it on its own,
	// whatever the caller checked, and the refusal leaves it pending and unconsumed.
	if err := repo.ConsumeOpLoginRequest(ctx, id, now); !errors.Is(err, api.ErrNotFound) {
		t.Fatalf("consume pending = %v, want ErrNotFound", err)
	}
	if req, err := repo.OpLoginRequestByID(ctx, id); err != nil || req.Status != "pending" || req.Consumed {
		t.Fatalf("after refused consume = %+v, %v; want pending+unconsumed", req, err)
	}

	// Approve -> consume -> single use; second approve/consume are ErrNotFound.
	if err := repo.ApproveOpLogin(ctx, id, approver.ID, now); err != nil {
		t.Fatalf("ApproveOpLogin: %v", err)