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  }