github.com/letsencrypt/boulder@v0.20251208.0/ratelimits/limiter_test.go (about)

     1  package ratelimits
     2  
     3  import (
     4  	"context"
     5  	"math/rand/v2"
     6  	"net"
     7  	"net/netip"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/jmhodges/clock"
    12  
    13  	"github.com/letsencrypt/boulder/config"
    14  	berrors "github.com/letsencrypt/boulder/errors"
    15  	blog "github.com/letsencrypt/boulder/log"
    16  	"github.com/letsencrypt/boulder/metrics"
    17  	"github.com/letsencrypt/boulder/test"
    18  )
    19  
    20  // overriddenIP is overridden in 'testdata/working_override.yml' to have higher
    21  // burst and count values.
    22  const overriddenIP = "64.112.117.1"
    23  
    24  // newTestLimiter constructs a new limiter.
    25  func newTestLimiter(t *testing.T, s Source, clk clock.FakeClock) *Limiter {
    26  	l, err := NewLimiter(clk, s, metrics.NoopRegisterer)
    27  	test.AssertNotError(t, err, "should not error")
    28  	return l
    29  }
    30  
    31  // newTestTransactionBuilder constructs a new *TransactionBuilder with the
    32  // following configuration:
    33  //   - 'NewRegistrationsPerIPAddress' burst: 20 count: 20 period: 1s
    34  //   - 'NewRegistrationsPerIPAddress:64.112.117.1' burst: 40 count: 40 period: 1s
    35  func newTestTransactionBuilder(t *testing.T) *TransactionBuilder {
    36  	c, err := NewTransactionBuilderFromFiles("testdata/working_default.yml", "testdata/working_override.yml", metrics.NoopRegisterer, blog.NewMock())
    37  	test.AssertNotError(t, err, "should not error")
    38  	err = c.loadOverrides(context.Background())
    39  	test.AssertNotError(t, err, "loading overrides")
    40  
    41  	return c
    42  }
    43  
    44  func setup(t *testing.T) (context.Context, map[string]*Limiter, *TransactionBuilder, clock.FakeClock, string) {
    45  	testCtx := context.Background()
    46  	clk := clock.NewFake()
    47  
    48  	// Generate a random IP address to avoid collisions during and between test
    49  	// runs.
    50  	randIP := make(net.IP, 4)
    51  	for i := range 4 {
    52  		randIP[i] = byte(rand.IntN(256))
    53  	}
    54  
    55  	// Construct a limiter for each source.
    56  	return testCtx, map[string]*Limiter{
    57  		"inmem": newInmemTestLimiter(t, clk),
    58  		"redis": newRedisTestLimiter(t, clk),
    59  	}, newTestTransactionBuilder(t), clk, randIP.String()
    60  }
    61  
    62  func resetBucket(t *testing.T, l *Limiter, ctx context.Context, limit *Limit, bucketKey string) {
    63  	t.Helper()
    64  	txn, err := newResetTransaction(limit, bucketKey)
    65  	test.AssertNotError(t, err, "txn should be valid")
    66  	err = l.BatchReset(ctx, []Transaction{txn})
    67  	test.AssertNotError(t, err, "should not error")
    68  }
    69  
    70  func TestLimiter_CheckWithLimitOverrides(t *testing.T) {
    71  	t.Parallel()
    72  	testCtx, limiters, txnBuilder, clk, testIP := setup(t)
    73  	for name, l := range limiters {
    74  		t.Run(name, func(t *testing.T) {
    75  			overriddenBucketKey := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(overriddenIP))
    76  			overriddenLimit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, overriddenBucketKey)
    77  			test.AssertNotError(t, err, "should not error")
    78  
    79  			// Attempt to spend all 40 requests, this should succeed.
    80  			overriddenTxn40, err := newTransaction(overriddenLimit, overriddenBucketKey, 40)
    81  			test.AssertNotError(t, err, "txn should be valid")
    82  			d, err := l.Spend(testCtx, overriddenTxn40)
    83  			test.AssertNotError(t, err, "should not error")
    84  			test.Assert(t, d.allowed, "should be allowed")
    85  
    86  			// Attempting to spend 1 more, this should fail.
    87  			overriddenTxn1, err := newTransaction(overriddenLimit, overriddenBucketKey, 1)
    88  			test.AssertNotError(t, err, "txn should be valid")
    89  			d, err = l.Spend(testCtx, overriddenTxn1)
    90  			test.AssertNotError(t, err, "should not error")
    91  			test.Assert(t, !d.allowed, "should not be allowed")
    92  			test.AssertEquals(t, d.remaining, int64(0))
    93  			test.AssertEquals(t, d.resetIn, time.Second)
    94  
    95  			// Verify our RetryIn is correct. 1 second == 1000 milliseconds and
    96  			// 1000/40 = 25 milliseconds per request.
    97  			test.AssertEquals(t, d.retryIn, time.Millisecond*25)
    98  
    99  			// Wait 50 milliseconds and try again.
   100  			clk.Add(d.retryIn)
   101  
   102  			// We should be allowed to spend 1 more request.
   103  			d, err = l.Spend(testCtx, overriddenTxn1)
   104  			test.AssertNotError(t, err, "should not error")
   105  			test.Assert(t, d.allowed, "should be allowed")
   106  			test.AssertEquals(t, d.remaining, int64(0))
   107  			test.AssertEquals(t, d.resetIn, time.Second)
   108  
   109  			// Wait 1 second for a full bucket reset.
   110  			clk.Add(d.resetIn)
   111  
   112  			// Quickly spend 40 requests in a row.
   113  			for i := range 40 {
   114  				d, err = l.Spend(testCtx, overriddenTxn1)
   115  				test.AssertNotError(t, err, "should not error")
   116  				test.Assert(t, d.allowed, "should be allowed")
   117  				test.AssertEquals(t, d.remaining, int64(39-i))
   118  			}
   119  
   120  			// Attempting to spend 1 more, this should fail.
   121  			d, err = l.Spend(testCtx, overriddenTxn1)
   122  			test.AssertNotError(t, err, "should not error")
   123  			test.Assert(t, !d.allowed, "should not be allowed")
   124  			test.AssertEquals(t, d.remaining, int64(0))
   125  			test.AssertEquals(t, d.resetIn, time.Second)
   126  
   127  			// Wait 1 second for a full bucket reset.
   128  			clk.Add(d.resetIn)
   129  
   130  			normalBucketKey := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
   131  			normalLimit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, normalBucketKey)
   132  			test.AssertNotError(t, err, "should not error")
   133  
   134  			// Spend the same bucket but in a batch with bucket subject to
   135  			// default limits. This should succeed, but the decision should
   136  			// reflect that of the default bucket.
   137  			defaultTxn1, err := newTransaction(normalLimit, normalBucketKey, 1)
   138  			test.AssertNotError(t, err, "txn should be valid")
   139  			d, err = l.BatchSpend(testCtx, []Transaction{overriddenTxn1, defaultTxn1})
   140  			test.AssertNotError(t, err, "should not error")
   141  			test.Assert(t, d.allowed, "should be allowed")
   142  			test.AssertEquals(t, d.remaining, int64(19))
   143  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   144  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   145  
   146  			// Refund quota to both buckets. This should succeed, but the
   147  			// decision should reflect that of the default bucket.
   148  			d, err = l.BatchRefund(testCtx, []Transaction{overriddenTxn1, defaultTxn1})
   149  			test.AssertNotError(t, err, "should not error")
   150  			test.Assert(t, d.allowed, "should be allowed")
   151  			test.AssertEquals(t, d.remaining, int64(20))
   152  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   153  			test.AssertEquals(t, d.resetIn, time.Duration(0))
   154  
   155  			// Once more.
   156  			d, err = l.BatchSpend(testCtx, []Transaction{overriddenTxn1, defaultTxn1})
   157  			test.AssertNotError(t, err, "should not error")
   158  			test.Assert(t, d.allowed, "should be allowed")
   159  			test.AssertEquals(t, d.remaining, int64(19))
   160  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   161  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   162  
   163  			// Reset between tests.
   164  			resetBucket(t, l, testCtx, overriddenLimit, overriddenBucketKey)
   165  			resetBucket(t, l, testCtx, normalLimit, normalBucketKey)
   166  
   167  			// Spend the same bucket but in a batch with a Transaction that is
   168  			// check-only. This should succeed, but the decision should reflect
   169  			// that of the default bucket.
   170  			defaultCheckOnlyTxn1, err := newCheckOnlyTransaction(normalLimit, normalBucketKey, 1)
   171  			test.AssertNotError(t, err, "txn should be valid")
   172  			d, err = l.BatchSpend(testCtx, []Transaction{overriddenTxn1, defaultCheckOnlyTxn1})
   173  			test.AssertNotError(t, err, "should not error")
   174  			test.AssertEquals(t, d.remaining, int64(19))
   175  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   176  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   177  
   178  			// Check the remaining quota of the overridden bucket.
   179  			overriddenCheckOnlyTxn0, err := newCheckOnlyTransaction(overriddenLimit, overriddenBucketKey, 0)
   180  			test.AssertNotError(t, err, "txn should be valid")
   181  			d, err = l.Check(testCtx, overriddenCheckOnlyTxn0)
   182  			test.AssertNotError(t, err, "should not error")
   183  			test.AssertEquals(t, d.remaining, int64(39))
   184  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   185  			test.AssertEquals(t, d.resetIn, time.Millisecond*25)
   186  
   187  			// Check the remaining quota of the default bucket.
   188  			defaultTxn0, err := newTransaction(normalLimit, normalBucketKey, 0)
   189  			test.AssertNotError(t, err, "txn should be valid")
   190  			d, err = l.Check(testCtx, defaultTxn0)
   191  			test.AssertNotError(t, err, "should not error")
   192  			test.AssertEquals(t, d.remaining, int64(20))
   193  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   194  			test.AssertEquals(t, d.resetIn, time.Duration(0))
   195  
   196  			// Spend the same bucket but in a batch with a Transaction that is
   197  			// spend-only. This should succeed, but the decision should reflect
   198  			// that of the overridden bucket.
   199  			defaultSpendOnlyTxn1, err := newSpendOnlyTransaction(normalLimit, normalBucketKey, 1)
   200  			test.AssertNotError(t, err, "txn should be valid")
   201  			d, err = l.BatchSpend(testCtx, []Transaction{overriddenTxn1, defaultSpendOnlyTxn1})
   202  			test.AssertNotError(t, err, "should not error")
   203  			test.AssertEquals(t, d.remaining, int64(38))
   204  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   205  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   206  
   207  			// Check the remaining quota of the overridden bucket.
   208  			d, err = l.Check(testCtx, overriddenCheckOnlyTxn0)
   209  			test.AssertNotError(t, err, "should not error")
   210  			test.AssertEquals(t, d.remaining, int64(38))
   211  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   212  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   213  
   214  			// Check the remaining quota of the default bucket.
   215  			d, err = l.Check(testCtx, defaultTxn0)
   216  			test.AssertNotError(t, err, "should not error")
   217  			test.AssertEquals(t, d.remaining, int64(19))
   218  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   219  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   220  
   221  			// Once more, but in now the spend-only Transaction will attempt to
   222  			// spend 20 requests. The spend-only Transaction should fail, but
   223  			// the decision should reflect that of the overridden bucket.
   224  			defaultSpendOnlyTxn20, err := newSpendOnlyTransaction(normalLimit, normalBucketKey, 20)
   225  			test.AssertNotError(t, err, "txn should be valid")
   226  			d, err = l.BatchSpend(testCtx, []Transaction{overriddenTxn1, defaultSpendOnlyTxn20})
   227  			test.AssertNotError(t, err, "should not error")
   228  			test.AssertEquals(t, d.remaining, int64(37))
   229  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   230  			test.AssertEquals(t, d.resetIn, time.Millisecond*75)
   231  
   232  			// Check the remaining quota of the overridden bucket.
   233  			d, err = l.Check(testCtx, overriddenCheckOnlyTxn0)
   234  			test.AssertNotError(t, err, "should not error")
   235  			test.AssertEquals(t, d.remaining, int64(37))
   236  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   237  			test.AssertEquals(t, d.resetIn, time.Millisecond*75)
   238  
   239  			// Check the remaining quota of the default bucket.
   240  			d, err = l.Check(testCtx, defaultTxn0)
   241  			test.AssertNotError(t, err, "should not error")
   242  			test.AssertEquals(t, d.remaining, int64(19))
   243  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   244  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   245  
   246  			// Reset between tests.
   247  			resetBucket(t, l, testCtx, overriddenLimit, overriddenBucketKey)
   248  		})
   249  	}
   250  }
   251  
   252  func TestLimiter_InitializationViaCheckAndSpend(t *testing.T) {
   253  	t.Parallel()
   254  	testCtx, limiters, txnBuilder, _, testIP := setup(t)
   255  	for name, l := range limiters {
   256  		t.Run(name, func(t *testing.T) {
   257  			bucketKey := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
   258  			limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
   259  			test.AssertNotError(t, err, "should not error")
   260  
   261  			// Check on an empty bucket should return the theoretical next state
   262  			// of that bucket if the cost were spent.
   263  			txn1, err := newTransaction(limit, bucketKey, 1)
   264  			test.AssertNotError(t, err, "txn should be valid")
   265  			d, err := l.Check(testCtx, txn1)
   266  			test.AssertNotError(t, err, "should not error")
   267  			test.Assert(t, d.allowed, "should be allowed")
   268  			test.AssertEquals(t, d.remaining, int64(19))
   269  			// Verify our ResetIn timing is correct. 1 second == 1000
   270  			// milliseconds and 1000/20 = 50 milliseconds per request.
   271  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   272  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   273  
   274  			// However, that cost should not be spent yet, a 0 cost check should
   275  			// tell us that we actually have 20 remaining.
   276  			txn0, err := newTransaction(limit, bucketKey, 0)
   277  			test.AssertNotError(t, err, "txn should be valid")
   278  			d, err = l.Check(testCtx, txn0)
   279  			test.AssertNotError(t, err, "should not error")
   280  			test.Assert(t, d.allowed, "should be allowed")
   281  			test.AssertEquals(t, d.remaining, int64(20))
   282  			test.AssertEquals(t, d.resetIn, time.Duration(0))
   283  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   284  
   285  			// Reset our bucket.
   286  			resetBucket(t, l, testCtx, limit, bucketKey)
   287  
   288  			// Similar to above, but we'll use Spend() to actually initialize
   289  			// the bucket. Spend should return the same result as Check.
   290  			d, err = l.Spend(testCtx, txn1)
   291  			test.AssertNotError(t, err, "should not error")
   292  			test.Assert(t, d.allowed, "should be allowed")
   293  			test.AssertEquals(t, d.remaining, int64(19))
   294  			// Verify our ResetIn timing is correct. 1 second == 1000
   295  			// milliseconds and 1000/20 = 50 milliseconds per request.
   296  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   297  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   298  
   299  			// And that cost should have been spent; a 0 cost check should still
   300  			// tell us that we have 19 remaining.
   301  			d, err = l.Check(testCtx, txn0)
   302  			test.AssertNotError(t, err, "should not error")
   303  			test.Assert(t, d.allowed, "should be allowed")
   304  			test.AssertEquals(t, d.remaining, int64(19))
   305  			// Verify our ResetIn is correct. 1 second == 1000 milliseconds and
   306  			// 1000/20 = 50 milliseconds per request.
   307  			test.AssertEquals(t, d.resetIn, time.Millisecond*50)
   308  			test.AssertEquals(t, d.retryIn, time.Duration(0))
   309  		})
   310  	}
   311  }
   312  
   313  func TestLimiter_DefaultLimits(t *testing.T) {
   314  	t.Parallel()
   315  	testCtx, limiters, txnBuilder, clk, testIP := setup(t)
   316  	for name, l := range limiters {
   317  		t.Run(name, func(t *testing.T) {
   318  			bucketKey := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
   319  			limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
   320  			test.AssertNotError(t, err, "should not error")
   321  
   322  			// Attempt to spend all 20 requests, this should succeed.
   323  			txn20, err := newTransaction(limit, bucketKey, 20)
   324  			test.AssertNotError(t, err, "txn should be valid")
   325  			d, err := l.Spend(testCtx, txn20)
   326  			test.AssertNotError(t, err, "should not error")
   327  			test.Assert(t, d.allowed, "should be allowed")
   328  			test.AssertEquals(t, d.remaining, int64(0))
   329  			test.AssertEquals(t, d.resetIn, time.Second)
   330  
   331  			// Attempting to spend 1 more, this should fail.
   332  			txn1, err := newTransaction(limit, bucketKey, 1)
   333  			test.AssertNotError(t, err, "txn should be valid")
   334  			d, err = l.Spend(testCtx, txn1)
   335  			test.AssertNotError(t, err, "should not error")
   336  			test.Assert(t, !d.allowed, "should not be allowed")
   337  			test.AssertEquals(t, d.remaining, int64(0))
   338  			test.AssertEquals(t, d.resetIn, time.Second)
   339  
   340  			// Verify our ResetIn is correct. 1 second == 1000 milliseconds and
   341  			// 1000/20 = 50 milliseconds per request.
   342  			test.AssertEquals(t, d.retryIn, time.Millisecond*50)
   343  
   344  			// Wait 50 milliseconds and try again.
   345  			clk.Add(d.retryIn)
   346  
   347  			// We should be allowed to spend 1 more request.
   348  			d, err = l.Spend(testCtx, txn1)
   349  			test.AssertNotError(t, err, "should not error")
   350  			test.Assert(t, d.allowed, "should be allowed")
   351  			test.AssertEquals(t, d.remaining, int64(0))
   352  			test.AssertEquals(t, d.resetIn, time.Second)
   353  
   354  			// Wait 1 second for a full bucket reset.
   355  			clk.Add(d.resetIn)
   356  
   357  			// Quickly spend 20 requests in a row.
   358  			for i := range 20 {
   359  				d, err = l.Spend(testCtx, txn1)
   360  				test.AssertNotError(t, err, "should not error")
   361  				test.Assert(t, d.allowed, "should be allowed")
   362  				test.AssertEquals(t, d.remaining, int64(19-i))
   363  			}
   364  
   365  			// Attempting to spend 1 more, this should fail.
   366  			d, err = l.Spend(testCtx, txn1)
   367  			test.AssertNotError(t, err, "should not error")
   368  			test.Assert(t, !d.allowed, "should not be allowed")
   369  			test.AssertEquals(t, d.remaining, int64(0))
   370  			test.AssertEquals(t, d.resetIn, time.Second)
   371  		})
   372  	}
   373  }
   374  
   375  func TestLimiter_RefundAndReset(t *testing.T) {
   376  	t.Parallel()
   377  	testCtx, limiters, txnBuilder, clk, testIP := setup(t)
   378  	for name, l := range limiters {
   379  		t.Run(name, func(t *testing.T) {
   380  			bucketKey := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
   381  			limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
   382  			test.AssertNotError(t, err, "should not error")
   383  
   384  			// Attempt to spend all 20 requests, this should succeed.
   385  			txn20, err := newTransaction(limit, bucketKey, 20)
   386  			test.AssertNotError(t, err, "txn should be valid")
   387  			d, err := l.Spend(testCtx, txn20)
   388  			test.AssertNotError(t, err, "should not error")
   389  			test.Assert(t, d.allowed, "should be allowed")
   390  			test.AssertEquals(t, d.remaining, int64(0))
   391  			test.AssertEquals(t, d.resetIn, time.Second)
   392  
   393  			// Refund 10 requests.
   394  			txn10, err := newTransaction(limit, bucketKey, 10)
   395  			test.AssertNotError(t, err, "txn should be valid")
   396  			d, err = l.Refund(testCtx, txn10)
   397  			test.AssertNotError(t, err, "should not error")
   398  			test.AssertEquals(t, d.remaining, int64(10))
   399  
   400  			// Spend 10 requests, this should succeed.
   401  			d, err = l.Spend(testCtx, txn10)
   402  			test.AssertNotError(t, err, "should not error")
   403  			test.Assert(t, d.allowed, "should be allowed")
   404  			test.AssertEquals(t, d.remaining, int64(0))
   405  			test.AssertEquals(t, d.resetIn, time.Second)
   406  
   407  			resetBucket(t, l, testCtx, limit, bucketKey)
   408  
   409  			// Attempt to spend 20 more requests, this should succeed.
   410  			d, err = l.Spend(testCtx, txn20)
   411  			test.AssertNotError(t, err, "should not error")
   412  			test.Assert(t, d.allowed, "should be allowed")
   413  			test.AssertEquals(t, d.remaining, int64(0))
   414  			test.AssertEquals(t, d.resetIn, time.Second)
   415  
   416  			// Reset to full.
   417  			clk.Add(d.resetIn)
   418  
   419  			// Refund 1 requests above our limit, this should fail.
   420  			txn1, err := newTransaction(limit, bucketKey, 1)
   421  			test.AssertNotError(t, err, "txn should be valid")
   422  			d, err = l.Refund(testCtx, txn1)
   423  			test.AssertNotError(t, err, "should not error")
   424  			test.Assert(t, !d.allowed, "should not be allowed")
   425  			test.AssertEquals(t, d.remaining, int64(20))
   426  
   427  			// Spend so we can refund.
   428  			_, err = l.Spend(testCtx, txn1)
   429  			test.AssertNotError(t, err, "should not error")
   430  
   431  			// Refund a spendOnly Transaction, which should succeed.
   432  			spendOnlyTxn1, err := newSpendOnlyTransaction(limit, bucketKey, 1)
   433  			test.AssertNotError(t, err, "txn should be valid")
   434  			_, err = l.Refund(testCtx, spendOnlyTxn1)
   435  			test.AssertNotError(t, err, "should not error")
   436  
   437  			// Spend so we can refund.
   438  			expectedDecision, err := l.Spend(testCtx, txn1)
   439  			test.AssertNotError(t, err, "should not error")
   440  
   441  			// Refund a checkOnly Transaction, which shouldn't error but should
   442  			// return the same TAT as the previous spend.
   443  			checkOnlyTxn1, err := newCheckOnlyTransaction(limit, bucketKey, 1)
   444  			test.AssertNotError(t, err, "txn should be valid")
   445  			newDecision, err := l.Refund(testCtx, checkOnlyTxn1)
   446  			test.AssertNotError(t, err, "should not error")
   447  			test.AssertEquals(t, newDecision.newTAT, expectedDecision.newTAT)
   448  		})
   449  	}
   450  }
   451  
   452  func TestRateLimitError(t *testing.T) {
   453  	t.Parallel()
   454  	now := clock.NewFake().Now()
   455  
   456  	testCases := []struct {
   457  		name            string
   458  		decision        *Decision
   459  		expectedErr     string
   460  		expectedErrType berrors.ErrorType
   461  	}{
   462  		{
   463  			name: "Allowed decision",
   464  			decision: &Decision{
   465  				allowed: true,
   466  			},
   467  		},
   468  		{
   469  			name: "RegistrationsPerIP limit reached",
   470  			decision: &Decision{
   471  				allowed: false,
   472  				retryIn: 5 * time.Second,
   473  				transaction: Transaction{
   474  					limit: &Limit{
   475  						Name:   NewRegistrationsPerIPAddress,
   476  						Burst:  10,
   477  						Period: config.Duration{Duration: time.Hour},
   478  					},
   479  				},
   480  			},
   481  			expectedErr:     "too many new registrations (10) from this IP address in the last 1h0m0s, retry after 1970-01-01 00:00:05 UTC: see https://letsencrypt.org/docs/rate-limits/#new-registrations-per-ip-address",
   482  			expectedErrType: berrors.RateLimit,
   483  		},
   484  		{
   485  			name: "RegistrationsPerIPv6Range limit reached",
   486  			decision: &Decision{
   487  				allowed: false,
   488  				retryIn: 10 * time.Second,
   489  				transaction: Transaction{
   490  					limit: &Limit{
   491  						Name:   NewRegistrationsPerIPv6Range,
   492  						Burst:  5,
   493  						Period: config.Duration{Duration: time.Hour},
   494  					},
   495  				},
   496  			},
   497  			expectedErr:     "too many new registrations (5) from this /48 subnet of IPv6 addresses in the last 1h0m0s, retry after 1970-01-01 00:00:10 UTC: see https://letsencrypt.org/docs/rate-limits/#new-registrations-per-ipv6-range",
   498  			expectedErrType: berrors.RateLimit,
   499  		},
   500  		{
   501  			name: "NewOrdersPerAccount limit reached",
   502  			decision: &Decision{
   503  				allowed: false,
   504  				retryIn: 10 * time.Second,
   505  				transaction: Transaction{
   506  					limit: &Limit{
   507  						Name:   NewOrdersPerAccount,
   508  						Burst:  2,
   509  						Period: config.Duration{Duration: time.Hour},
   510  					},
   511  				},
   512  			},
   513  			expectedErr:     "too many new orders (2) from this account in the last 1h0m0s, retry after 1970-01-01 00:00:10 UTC: see https://letsencrypt.org/docs/rate-limits/#new-orders-per-account",
   514  			expectedErrType: berrors.RateLimit,
   515  		},
   516  		{
   517  			name: "FailedAuthorizationsPerDomainPerAccount limit reached",
   518  			decision: &Decision{
   519  				allowed: false,
   520  				retryIn: 15 * time.Second,
   521  				transaction: Transaction{
   522  					limit: &Limit{
   523  						Name:   FailedAuthorizationsPerDomainPerAccount,
   524  						Burst:  7,
   525  						Period: config.Duration{Duration: time.Hour},
   526  					},
   527  					bucketKey: "4:12345:example.com",
   528  				},
   529  			},
   530  			expectedErr:     "too many failed authorizations (7) for \"example.com\" in the last 1h0m0s, retry after 1970-01-01 00:00:15 UTC: see https://letsencrypt.org/docs/rate-limits/#authorization-failures-per-hostname-per-account",
   531  			expectedErrType: berrors.RateLimit,
   532  		},
   533  		{
   534  			name: "CertificatesPerDomain limit reached",
   535  			decision: &Decision{
   536  				allowed: false,
   537  				retryIn: 20 * time.Second,
   538  				transaction: Transaction{
   539  					limit: &Limit{
   540  						Name:   CertificatesPerDomain,
   541  						Burst:  3,
   542  						Period: config.Duration{Duration: time.Hour},
   543  					},
   544  					bucketKey: "5:example.org",
   545  				},
   546  			},
   547  			expectedErr:     "too many certificates (3) already issued for \"example.org\" in the last 1h0m0s, retry after 1970-01-01 00:00:20 UTC: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-registered-domain",
   548  			expectedErrType: berrors.RateLimit,
   549  		},
   550  		{
   551  			name: "CertificatesPerDomainPerAccount limit reached",
   552  			decision: &Decision{
   553  				allowed: false,
   554  				retryIn: 20 * time.Second,
   555  				transaction: Transaction{
   556  					limit: &Limit{
   557  						Name:   CertificatesPerDomainPerAccount,
   558  						Burst:  3,
   559  						Period: config.Duration{Duration: time.Hour},
   560  					},
   561  					bucketKey: "6:12345678:example.net",
   562  				},
   563  			},
   564  			expectedErr:     "too many certificates (3) already issued for \"example.net\" in the last 1h0m0s, retry after 1970-01-01 00:00:20 UTC: see https://letsencrypt.org/docs/rate-limits/#new-certificates-per-registered-domain",
   565  			expectedErrType: berrors.RateLimit,
   566  		},
   567  		{
   568  			name: "LimitOverrideRequestsPerIPAddress limit reached",
   569  			decision: &Decision{
   570  				allowed: false,
   571  				retryIn: 20 * time.Second,
   572  				transaction: Transaction{
   573  					limit: &Limit{
   574  						Name:   LimitOverrideRequestsPerIPAddress,
   575  						Burst:  3,
   576  						Period: config.Duration{Duration: time.Hour},
   577  					},
   578  				},
   579  			},
   580  			expectedErr:     "too many override request form submissions (3) from this IP address in the last 1h0m0s, retry after 1970-01-01 00:00:20 UTC: see https://letsencrypt.org/docs/rate-limits/#limit-override-requests-per-ip-address",
   581  			expectedErrType: berrors.RateLimit,
   582  		},
   583  		{
   584  			name: "Unknown rate limit name",
   585  			decision: &Decision{
   586  				allowed: false,
   587  				retryIn: 30 * time.Second,
   588  				transaction: Transaction{
   589  					limit: &Limit{
   590  						Name: 9999999,
   591  					},
   592  				},
   593  			},
   594  			expectedErr:     "cannot generate error for unknown rate limit",
   595  			expectedErrType: berrors.InternalServer,
   596  		},
   597  	}
   598  
   599  	for _, tc := range testCases {
   600  		t.Run(tc.name, func(t *testing.T) {
   601  			t.Parallel()
   602  			err := tc.decision.Result(now)
   603  			if tc.expectedErr == "" {
   604  				test.AssertNotError(t, err, "expected no error")
   605  			} else {
   606  				test.AssertError(t, err, "expected an error")
   607  				test.AssertEquals(t, err.Error(), tc.expectedErr)
   608  				test.AssertErrorIs(t, err, tc.expectedErrType)
   609  			}
   610  		})
   611  	}
   612  }