github.com/letsencrypt/boulder@v0.20251208.0/cmd/admin/pause_identifier_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "path" 8 "strings" 9 "testing" 10 11 blog "github.com/letsencrypt/boulder/log" 12 sapb "github.com/letsencrypt/boulder/sa/proto" 13 "github.com/letsencrypt/boulder/test" 14 "google.golang.org/grpc" 15 ) 16 17 func TestReadingPauseCSV(t *testing.T) { 18 t.Parallel() 19 20 testCases := []struct { 21 name string 22 data []string 23 expectedRecords int 24 }{ 25 { 26 name: "No data in file", 27 data: nil, 28 }, 29 { 30 name: "valid", 31 data: []string{"1,dns,example.com"}, 32 expectedRecords: 1, 33 }, 34 { 35 name: "valid with duplicates", 36 data: []string{"1,dns,example.com", "2,dns,example.org", "1,dns,example.com", "1,dns,example.net", "3,dns,example.gov", "3,dns,example.gov"}, 37 expectedRecords: 6, 38 }, 39 { 40 name: "invalid with multiple domains on the same line", 41 data: []string{"1,dns,example.com,example.net"}, 42 }, 43 { 44 name: "invalid just commas", 45 data: []string{",,,"}, 46 }, 47 { 48 name: "invalid only contains accountID", 49 data: []string{"1"}, 50 }, 51 { 52 name: "invalid only contains accountID and identifierType", 53 data: []string{"1,dns"}, 54 }, 55 { 56 name: "invalid missing identifierType", 57 data: []string{"1,,example.com"}, 58 }, 59 { 60 name: "invalid accountID isnt an int", 61 data: []string{"blorple"}, 62 }, 63 } 64 65 for _, testCase := range testCases { 66 t.Run(testCase.name, func(t *testing.T) { 67 t.Parallel() 68 log := blog.NewMock() 69 a := admin{log: log} 70 71 csvFile := path.Join(t.TempDir(), path.Base(t.Name()+".csv")) 72 err := os.WriteFile(csvFile, []byte(strings.Join(testCase.data, "\n")), os.ModePerm) 73 test.AssertNotError(t, err, "could not write temporary file") 74 75 parsedData, err := a.readPausedAccountFile(csvFile) 76 test.AssertNotError(t, err, "no error expected, but received one") 77 test.AssertEquals(t, len(parsedData), testCase.expectedRecords) 78 }) 79 } 80 } 81 82 // mockSAPaused is a mock which always succeeds. It records the PauseRequest it 83 // received, and returns the number of identifiers as a 84 // PauseIdentifiersResponse. It does not maintain state of repaused identifiers. 85 type mockSAPaused struct { 86 sapb.StorageAuthorityClient 87 } 88 89 func (msa *mockSAPaused) PauseIdentifiers(ctx context.Context, in *sapb.PauseRequest, _ ...grpc.CallOption) (*sapb.PauseIdentifiersResponse, error) { 90 return &sapb.PauseIdentifiersResponse{Paused: int64(len(in.Identifiers))}, nil 91 } 92 93 // mockSAPausedBroken is a mock which always errors. 94 type mockSAPausedBroken struct { 95 sapb.StorageAuthorityClient 96 } 97 98 func (msa *mockSAPausedBroken) PauseIdentifiers(ctx context.Context, in *sapb.PauseRequest, _ ...grpc.CallOption) (*sapb.PauseIdentifiersResponse, error) { 99 return nil, errors.New("its all jacked up") 100 } 101 102 func TestPauseIdentifiers(t *testing.T) { 103 t.Parallel() 104 105 testCases := []struct { 106 name string 107 data []pauseCSVData 108 saImpl sapb.StorageAuthorityClient 109 expectRespLen int 110 expectErr bool 111 }{ 112 { 113 name: "no data", 114 data: nil, 115 expectErr: true, 116 }, 117 { 118 name: "valid single entry", 119 data: []pauseCSVData{ 120 { 121 accountID: 1, 122 identifierType: "dns", 123 identifierValue: "example.com", 124 }, 125 }, 126 expectRespLen: 1, 127 }, 128 { 129 name: "valid single entry but broken SA", 130 expectErr: true, 131 saImpl: &mockSAPausedBroken{}, 132 data: []pauseCSVData{ 133 { 134 accountID: 1, 135 identifierType: "dns", 136 identifierValue: "example.com", 137 }, 138 }, 139 }, 140 { 141 name: "valid multiple entries with duplicates", 142 data: []pauseCSVData{ 143 { 144 accountID: 1, 145 identifierType: "dns", 146 identifierValue: "example.com", 147 }, 148 { 149 accountID: 1, 150 identifierType: "dns", 151 identifierValue: "example.com", 152 }, 153 { 154 accountID: 2, 155 identifierType: "dns", 156 identifierValue: "example.org", 157 }, 158 { 159 accountID: 3, 160 identifierType: "dns", 161 identifierValue: "example.net", 162 }, 163 { 164 accountID: 3, 165 identifierType: "dns", 166 identifierValue: "example.org", 167 }, 168 }, 169 expectRespLen: 3, 170 }, 171 } 172 173 for _, testCase := range testCases { 174 t.Run(testCase.name, func(t *testing.T) { 175 t.Parallel() 176 177 log := blog.NewMock() 178 179 // Default to a working mock SA implementation 180 if testCase.saImpl == nil { 181 testCase.saImpl = &mockSAPaused{} 182 } 183 a := admin{sac: testCase.saImpl, log: log} 184 185 responses, err := a.pauseIdentifiers(context.Background(), testCase.data, 10) 186 if testCase.expectErr { 187 test.AssertError(t, err, "should have errored, but did not") 188 } else { 189 test.AssertNotError(t, err, "should not have errored") 190 // Batching will consolidate identifiers under the same account. 191 test.AssertEquals(t, len(responses), testCase.expectRespLen) 192 } 193 }) 194 } 195 }