package updater

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"database/sql"
	"errors"
	"math/big"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/jmhodges/clock"
	capb "github.com/letsencrypt/boulder/ca/proto"
	"github.com/letsencrypt/boulder/core"
	"github.com/letsencrypt/boulder/db"
	bgrpc "github.com/letsencrypt/boulder/grpc"
	blog "github.com/letsencrypt/boulder/log"
	"github.com/letsencrypt/boulder/metrics"
	rocsp_config "github.com/letsencrypt/boulder/rocsp/config"
	"github.com/letsencrypt/boulder/sa"
	sapb "github.com/letsencrypt/boulder/sa/proto"
	"github.com/letsencrypt/boulder/sa/satest"
	"github.com/letsencrypt/boulder/test"
	isa "github.com/letsencrypt/boulder/test/inmem/sa"
	"github.com/letsencrypt/boulder/test/vars"
	"github.com/prometheus/client_golang/prometheus"
	"google.golang.org/grpc"
)

var ctx = context.Background()

type mockOCSP struct {
	sleepTime time.Duration
}

func (ca *mockOCSP) GenerateOCSP(_ context.Context, req *capb.GenerateOCSPRequest, _ ...grpc.CallOption) (*capb.OCSPResponse, error) {
	time.Sleep(ca.sleepTime)
	return &capb.OCSPResponse{Response: []byte{1, 2, 3}}, nil
}

type noopROCSP struct {
}

func (noopROCSP) StoreResponse(_ context.Context, _ []byte, _ byte) error {
	return nil
}

var log = blog.UseMock()

func setup(t *testing.T) (*OCSPUpdater, sapb.StorageAuthorityClient, *db.WrappedMap, clock.FakeClock, func()) {
	dbMap, err := sa.NewDbMap(vars.DBConnSA, sa.DbSettings{})
	test.AssertNotError(t, err, "Failed to create dbMap")
	readOnlyDb, err := sa.NewDbMap(vars.DBConnSAOcspUpdateRO, sa.DbSettings{})
	test.AssertNotError(t, err, "Failed to create dbMap")
	cleanUp := test.ResetSATestDatabase(t)
	sa.SetSQLDebug(dbMap, log)

	fc := clock.NewFake()
	fc.Add(1 * time.Hour)

	sa, err := sa.NewSQLStorageAuthority(dbMap, dbMap, nil, nil, fc, log, metrics.NoopRegisterer, 1)
	test.AssertNotError(t, err, "Failed to create SA")

	updater, err := New(
		metrics.NoopRegisterer,
		fc,
		dbMap,
		readOnlyDb,
		noopROCSP{},
		nil,
		strings.Fields("0 1 2 3 4 5 6 7 8 9 a b c d e f"),
		&mockOCSP{},
		1,
		time.Second,
		time.Minute,
		1.5,
		0,
		0,
		0,
		blog.NewMock(),
	)
	test.AssertNotError(t, err, "Failed to create newUpdater")

	return updater, isa.SA{Impl: sa}, dbMap, fc, cleanUp
}

func nowNano(fc clock.Clock) int64 {
	return fc.Now().UnixNano()
}

func TestStalenessHistogram(t *testing.T) {
	updater, sac, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sac)
	parsedCertA, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sac.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCertA.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")
	parsedCertB, err := core.LoadCert("testdata/test-cert-b.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sac.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCertB.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert-b.pem")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	// We should have 2 stale responses now.
	statuses := updater.findStaleOCSPResponses(ctx, earliest, 10)
	var statusSlice []sa.CertStatusMetadata
	for status := range statuses {
		statusSlice = append(statusSlice, status)
	}
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statusSlice), 2)

	test.AssertMetricWithLabelsEquals(t, updater.stalenessHistogram, prometheus.Labels{}, 2)
}

func TestGenerateAndStoreOCSPResponse(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)
	status := <-statuses

	meta, err := updater.generateResponse(ctx, status)
	test.AssertNotError(t, err, "Couldn't generate OCSP response")
	err = updater.storeResponse(context.Background(), meta)
	test.AssertNotError(t, err, "Couldn't store certificate status")
}

type rocspStorage struct {
	shortIDIssuer byte
	response      []byte
}

type recordingROCSP struct {
	sync.Mutex
	storage []rocspStorage
}

func (rr *recordingROCSP) get() []rocspStorage {
	rr.Lock()
	defer rr.Unlock()
	var ret []rocspStorage
	return append(ret, rr.storage...)
}

func (rr *recordingROCSP) StoreResponse(ctx context.Context, respBytes []byte, shortIssuerID byte) error {
	rr.Lock()
	defer rr.Unlock()
	rr.storage = append(rr.storage, rocspStorage{
		shortIDIssuer: shortIssuerID,
		response:      respBytes,
	})
	return nil
}

// A mock ocspDb that sleeps for 50ms when Exec is called.
type mockDBBlocksOnExec struct{}

func (mdboe *mockDBBlocksOnExec) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return nil, nil
}

func (mdboe *mockDBBlocksOnExec) Exec(query string, args ...interface{}) (sql.Result, error) {
	time.Sleep(500 * time.Millisecond)
	return nil, nil
}

func TestROCSP(t *testing.T) {
	updater, sac, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sac)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sac.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 66283756913588288,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	recorder := &recordingROCSP{}
	updater.rocspClient = recorder
	updater.issuers, err = rocsp_config.LoadIssuers(
		map[string]int{
			"../../test/hierarchy/int-e1.cert.pem": 23,
		},
	)
	test.AssertNotError(t, err, "loading issuers")
	updater.db = &mockDBBlocksOnExec{}

	err = updater.storeResponse(context.Background(), &sa.CertStatusMetadata{
		CertificateStatus: core.CertificateStatus{
			OCSPResponse: []byte("fake response"),
			Serial:       "fake serial",
			IssuerID:     66283756913588288,
		},
	})
	test.AssertNotError(t, err, "Couldn't store certificate status")
	storage := recorder.get()
	test.AssertEquals(t, len(storage), 1)

	test.AssertByteEquals(t, storage[0].response, []byte("fake response"))
}

// findStaleOCSPResponsesBuffered runs findStaleOCSPResponses and returns
// it as a buffered channel. This is helpful for tests that want to test
// the length of the channel.
func findStaleOCSPResponsesBuffered(ctx context.Context, updater *OCSPUpdater, earliest time.Time, batchSize int) <-chan sa.CertStatusMetadata {
	statuses := make(chan sa.CertStatusMetadata, batchSize)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer close(statuses)
		s := updater.findStaleOCSPResponses(ctx, earliest, 10)
		for status := range s {
			statuses <- status
		}
	}()
	wg.Wait()
	return statuses
}

func TestGenerateOCSPResponses(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCertA, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCertA.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")
	parsedCertB, err := core.LoadCert("testdata/test-cert-b.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCertB.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert-b.pem")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	// We should have 2 stale responses now.
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 2)

	// Hacky test of parallelism: Make each request to the CA take 1 second, and
	// produce 2 requests to the CA. If the pair of requests complete in about a
	// second, they were made in parallel.
	// Note that this test also tests the basic functionality of
	// generateOCSPResponses.
	start := time.Now()
	updater.ogc = &mockOCSP{time.Second}
	updater.parallelGenerateOCSPRequests = 10
	updater.generateOCSPResponses(ctx, statuses)
	elapsed := time.Since(start)
	if elapsed > 1500*time.Millisecond {
		t.Errorf("generateOCSPResponses took too long, expected it to make calls in parallel.")
	}

	// generateOCSPResponses should have updated the ocspLastUpdate for each
	// cert, so there shouldn't be any stale responses anymore.
	statuses = findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)

	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 0)
}

func TestFindStaleOCSPResponses(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	// With no rows in the CertificateStatus table we shouldn't get an error.
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, fc.Now(), 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 0)

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	// We should have 1 stale response now.
	statuses = findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)
	status := <-statuses

	// Generate and store an updated response, which will update the
	// ocspLastUpdate field for this cert.
	meta, err := updater.generateResponse(ctx, status)
	test.AssertNotError(t, err, "Couldn't generate OCSP response")
	err = updater.storeResponse(context.Background(), meta)
	test.AssertNotError(t, err, "Couldn't store OCSP response")

	// We should have 0 stale responses now.
	statuses = findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 0)
}

func TestFindStaleOCSPResponsesRevokedReason(t *testing.T) {
	updater, sa, dbMap, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	// Set a revokedReason to ensure it gets written into the OCSPResponse.
	_, err = dbMap.Exec(
		"UPDATE certificateStatus SET revokedReason = 1 WHERE serial = ?",
		core.SerialToString(parsedCert.SerialNumber))
	test.AssertNotError(t, err, "Couldn't update revokedReason")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	statuses := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)
	status := <-statuses
	test.AssertEquals(t, int(status.RevokedReason), 1)
}

func TestPipelineTick(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	updater.ocspMinTimeToExpiry = 1 * time.Hour
	earliest := fc.Now().Add(-time.Hour)
	updater.generateOCSPResponses(ctx, updater.processExpired(ctx, updater.findStaleOCSPResponses(ctx, earliest, 10)))
	test.AssertEquals(t, updater.readFailures.Value(), 0)

	certs := findStaleOCSPResponsesBuffered(ctx, updater, fc.Now().Add(-updater.ocspMinTimeToExpiry), 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(certs), 0)
}

// TestProcessExpired checks that the `processExpired` pipeline step
// updates the `IsExpired` field opportunistically as it encounters
// certificates that are expired but whose certificate status rows do not
// have `IsExpired` set, and that expired certs don't show up as having
// stale responses.
func TestProcessExpired(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	serial := core.SerialToString(parsedCert.SerialNumber)

	// Add a new test certificate
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	// The certificate isn't expired, so the certificate status should have
	// a false `IsExpired` and it should show up as stale.
	statusPB, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
	test.AssertNotError(t, err, "Couldn't get the certificateStatus from the database")
	cs, err := bgrpc.PBToCertStatus(statusPB)
	test.AssertNotError(t, err, "Count't convert the certificateStatus from a PB")

	test.AssertEquals(t, cs.IsExpired, false)
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)

	// Advance the clock to the point that the certificate we added is now expired
	fc.Set(parsedCert.NotAfter.Add(2 * time.Hour))
	earliest = fc.Now().Add(-time.Hour)
	updater.ocspMinTimeToExpiry = 1 * time.Hour

	// Run pipeline to find stale responses, mark expired, and generate new response.
	updater.generateOCSPResponses(ctx, updater.processExpired(ctx, updater.findStaleOCSPResponses(ctx, earliest, 10)))

	// Since we advanced the fakeclock beyond our test certificate's NotAfter we
	// expect the certificate status has been updated to have a true `IsExpired`
	statusPB, err = sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
	test.AssertNotError(t, err, "Couldn't get the certificateStatus from the database")
	cs, err = bgrpc.PBToCertStatus(statusPB)
	test.AssertNotError(t, err, "Count't convert the certificateStatus from a PB")

	test.AssertEquals(t, cs.IsExpired, true)
	statuses = findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 0)
}

func TestStoreResponseGuard(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)
	parsedCert, err := core.LoadCert("testdata/test-cert.pem")
	test.AssertNotError(t, err, "Couldn't read test certificate")
	_, err = sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      parsedCert.Raw,
		RegID:    reg.Id,
		Ocsp:     nil,
		Issued:   nowNano(fc),
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert.pem")

	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)
	status := <-statuses

	serialStr := core.SerialToString(parsedCert.SerialNumber)
	reason := int64(0)
	revokedDate := fc.Now().UnixNano()
	_, err = sa.RevokeCertificate(context.Background(), &sapb.RevokeCertificateRequest{
		Serial:   serialStr,
		Reason:   reason,
		Date:     revokedDate,
		Response: []byte("fakeocspbytes"),
	})
	test.AssertNotError(t, err, "Failed to revoked certificate")

	// Attempt to update OCSP response where status.Status is good but stored status
	// is revoked, this should fail silently
	status.OCSPResponse = []byte("newfakeocspbytes")
	err = updater.storeResponse(context.Background(), &status)
	test.AssertNotError(t, err, "Failed to update certificate status")

	// Make sure the OCSP response hasn't actually changed
	unchangedStatus, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: core.SerialToString(parsedCert.SerialNumber)})
	test.AssertNotError(t, err, "Failed to get certificate status")
	test.AssertEquals(t, string(unchangedStatus.OcspResponse), "fakeocspbytes")

	// Changing the status to the stored status should allow the update to occur
	status.Status = core.OCSPStatusRevoked
	err = updater.storeResponse(context.Background(), &status)
	test.AssertNotError(t, err, "Failed to updated certificate status")

	// Make sure the OCSP response has been updated
	changedStatus, err := sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: core.SerialToString(parsedCert.SerialNumber)})
	test.AssertNotError(t, err, "Failed to get certificate status")
	test.AssertEquals(t, string(changedStatus.OcspResponse), "newfakeocspbytes")
}

func TestGenerateOCSPResponsePrecert(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()

	reg := satest.CreateWorkingRegistration(t, sa)

	// Create a throw-away self signed certificate with some names
	serial, testCert := test.ThrowAwayCert(t, 5)

	// Use AddPrecertificate to set up a precertificate, serials, and
	// certificateStatus row for the testcert.
	ocspResp := []byte{0, 0, 1}
	regID := reg.Id
	issuedTime := fc.Now().UnixNano()
	_, err := sa.AddPrecertificate(ctx, &sapb.AddCertificateRequest{
		Der:      testCert.Raw,
		RegID:    regID,
		Ocsp:     ocspResp,
		Issued:   issuedTime,
		IssuerID: 1,
	})
	test.AssertNotError(t, err, "Couldn't add test-cert2.der")

	// Jump time forward by 2 hours so the ocspLastUpdate value will be older than
	// the earliest lastUpdate time we care about.
	fc.Set(fc.Now().Add(2 * time.Hour))
	earliest := fc.Now().Add(-time.Hour)

	// There should be one stale ocsp response found for the precert
	certs := findStaleOCSPResponsesBuffered(ctx, updater, earliest, 10)
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(certs), 1)
	cert := <-certs
	test.AssertEquals(t, cert.Serial, serial)

	// Directly call generateResponse again with the same result. It should not
	// error and should instead update the precertificate's OCSP status even
	// though no certificate row exists.
	_, err = updater.generateResponse(ctx, cert)
	test.AssertNotError(t, err, "generateResponse for precert errored")
}

type mockOCSPRecordIssuer struct {
	gotIssuer bool
}

func (ca *mockOCSPRecordIssuer) GenerateOCSP(_ context.Context, req *capb.GenerateOCSPRequest, _ ...grpc.CallOption) (*capb.OCSPResponse, error) {
	ca.gotIssuer = req.IssuerID != 0 && req.Serial != ""
	return &capb.OCSPResponse{Response: []byte{1, 2, 3}}, nil
}

func TestIssuerInfo(t *testing.T) {
	updater, sa, _, fc, cleanUp := setup(t)
	defer cleanUp()
	m := mockOCSPRecordIssuer{}
	updater.ogc = &m
	reg := satest.CreateWorkingRegistration(t, sa)

	k, err := rsa.GenerateKey(rand.Reader, 512)
	test.AssertNotError(t, err, "rsa.GenerateKey failed")
	template := &x509.Certificate{
		SerialNumber: big.NewInt(1),
		DNSNames:     []string{"example.com"},
	}
	certA, err := x509.CreateCertificate(rand.Reader, template, template, &k.PublicKey, k)
	test.AssertNotError(t, err, "x509.CreateCertificate failed")

	now := fc.Now().UnixNano()
	id := int64(1234)
	_, err = sa.AddPrecertificate(context.Background(), &sapb.AddCertificateRequest{
		Der:      certA,
		RegID:    reg.Id,
		Ocsp:     []byte{1, 2, 3},
		Issued:   now,
		IssuerID: id,
	})
	test.AssertNotError(t, err, "sa.AddPrecertificate failed")

	fc.Add(time.Hour * 24 * 4)
	statuses := findStaleOCSPResponsesBuffered(ctx, updater, fc.Now().Add(-time.Hour), 10)

	test.AssertEquals(t, updater.readFailures.Value(), 0)
	test.AssertEquals(t, len(statuses), 1)
	status := <-statuses
	test.AssertEquals(t, status.IssuerID, id)

	_, err = updater.generateResponse(context.Background(), status)
	test.AssertNotError(t, err, "generateResponse failed")
	test.Assert(t, m.gotIssuer, "generateResponse didn't send issuer information and serial")
}

type brokenDB struct{}

func (bdb *brokenDB) Query(query string, args ...interface{}) (*sql.Rows, error) {
	return nil, errors.New("broken")
}
func (bdb *brokenDB) Exec(query string, args ...interface{}) (sql.Result, error) {
	return nil, errors.New("broken")
}

func TestTickSleep(t *testing.T) {
	updater, _, dbMap, fc, cleanUp := setup(t)
	defer cleanUp()
	m := &brokenDB{}
	updater.readOnlyDb = m

	// Test that when findStaleResponses fails the failure counter is
	// incremented and the clock moved forward by more than
	// updater.tickWindow
	updater.readFailures.Add(2)
	before := fc.Now()
	updater.Tick()
	test.AssertEquals(t, updater.readFailures.Value(), 3)
	took := fc.Since(before)
	test.Assert(t, took > updater.tickWindow, "Clock didn't move forward enough")

	// Test when findStaleResponses works the failure counter is reset to
	// zero and the clock only moves by updater.tickWindow
	updater.readOnlyDb = dbMap
	before = fc.Now()
	updater.Tick()
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	took = fc.Since(before)
	test.AssertEquals(t, took, updater.tickWindow)

}

func TestFindOCSPResponsesSleep(t *testing.T) {
	updater, _, dbMap, fc, cleanUp := setup(t)
	defer cleanUp()
	m := &brokenDB{}
	updater.readOnlyDb = m

	// Test when updateOCSPResponses fails the failure counter is incremented
	// and the clock moved forward by more than updater.tickWindow
	updater.readFailures.Add(2)
	before := fc.Now()
	updater.Tick()
	test.AssertEquals(t, updater.readFailures.Value(), 3)
	took := fc.Since(before)
	test.Assert(t, took > updater.tickWindow, "Clock didn't move forward enough")

	// Test when updateOCSPResponses works the failure counter is reset to zero
	// and the clock only moves by updater.tickWindow
	updater.readOnlyDb = dbMap
	before = fc.Now()
	updater.Tick()
	test.AssertEquals(t, updater.readFailures.Value(), 0)
	took = fc.Since(before)
	test.AssertEquals(t, took, updater.tickWindow)

}

func mkNewUpdaterWithStrings(t *testing.T, shards []string) (*OCSPUpdater, error) {
	dbMap, err := sa.NewDbMap(vars.DBConnSA, sa.DbSettings{})
	test.AssertNotError(t, err, "Failed to create dbMap")
	sa.SetSQLDebug(dbMap, log)

	fc := clock.NewFake()

	updater, err := New(
		metrics.NoopRegisterer,
		fc,
		dbMap,
		dbMap,
		noopROCSP{},
		nil,
		shards,
		&mockOCSP{},
		1,
		time.Second,
		time.Minute,
		1.5,
		0,
		0,
		0,
		blog.NewMock(),
	)
	return updater, err
}

func TestUpdaterConfiguration(t *testing.T) {
	_, err := mkNewUpdaterWithStrings(t, strings.Fields("0 1 2 3 4 5 6 7 8 9 a B c d e f"))
	test.AssertError(t, err, "No uppercase allowed")

	_, err = mkNewUpdaterWithStrings(t, strings.Fields("0 1 g"))
	test.AssertError(t, err, "No letters > f allowed")

	_, err = mkNewUpdaterWithStrings(t, strings.Fields("0 *"))
	test.AssertError(t, err, "No special chars allowed")

	_, err = mkNewUpdaterWithStrings(t, strings.Fields("0 -1"))
	test.AssertError(t, err, "No negative numbers allowed")

	_, err = mkNewUpdaterWithStrings(t, strings.Fields("wazzup 0 a b c"))
	test.AssertError(t, err, "No multi-letter shards allowed")

	_, err = mkNewUpdaterWithStrings(t, []string{})
	test.AssertNotError(t, err, "Empty should be valid, meaning use old queries")
}

func TestGetQuestionsForShardList(t *testing.T) {
	test.AssertEquals(t, getQuestionsForShardList(2), "?,?")
	test.AssertEquals(t, getQuestionsForShardList(1), "?")
	test.AssertEquals(t, getQuestionsForShardList(16), "?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?")
}
