package http import ( "context" "io" "mime" "mime/multipart" "net/http" "net/http/httptest" "testing" "github.com/golang/mock/gomock" "github.com/influxdata/influxdb/v2" influxdbcontext "github.com/influxdata/influxdb/v2/context" kithttp "github.com/influxdata/influxdb/v2/kit/transport/http" "github.com/influxdata/influxdb/v2/mock" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) func TestBackupMetaService(t *testing.T) { ctrlr := gomock.NewController(t) backupSvc := mock.NewMockBackupService(ctrlr) sqlBackupSvc := mock.NewMockSqlBackupRestoreService(ctrlr) bucketManifestWriter := mock.NewMockBucketManifestWriter(ctrlr) b := &BackupBackend{ BackupService: backupSvc, SqlBackupRestoreService: sqlBackupSvc, BucketManifestWriter: bucketManifestWriter, } h := NewBackupHandler(b) rr := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, "/", nil) require.NoError(t, err) backupSvc.EXPECT(). BackupKVStore(gomock.Any(), gomock.Any()). Return(nil) backupSvc.EXPECT().RLockKVStore() backupSvc.EXPECT().UnlockKVStore() sqlBackupSvc.EXPECT(). BackupSqlStore(gomock.Any(), gomock.Any()). Return(nil) sqlBackupSvc.EXPECT().RLockSqlStore() sqlBackupSvc.EXPECT().RUnlockSqlStore() bucketManifestWriter.EXPECT(). WriteManifest(gomock.Any(), gomock.Any()). Return(nil) h.handleBackupMetadata(rr, r) rs := rr.Result() require.Equal(t, rs.StatusCode, http.StatusOK) // Parse the multi-part response // First get the boundary from the header _, params, err := mime.ParseMediaType(rs.Header.Get("Content-Type")) require.NoError(t, err) mr := multipart.NewReader(rs.Body, params["boundary"]) // Go through the parts of the response and verify the part names appear in the correct order wantContentTypes := []string{"application/octet-stream", "application/octet-stream", "application/json; charset=utf-8"} wantPartNames := []string{"kv", "sql", "buckets"} for i := 0; ; i++ { p, err := mr.NextPart() if err == io.EOF { break } require.NoError(t, err) require.Equal(t, wantContentTypes[i], p.Header.Get("Content-Type")) _, params, err := mime.ParseMediaType(p.Header.Get("Content-Disposition")) require.NoError(t, err) require.Equal(t, wantPartNames[i], params["name"]) } } func TestRequireOperPermissions(t *testing.T) { tests := []struct { name string permList []influxdb.Permission wantStatus int wantContentType string }{ { "authorized to do the backup", influxdb.OperPermissions(), http.StatusOK, "text/plain; charset=utf-8", }, { "not authorized to do the backup", influxdb.ReadAllPermissions(), http.StatusUnauthorized, "application/json; charset=utf-8", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // next will only be called if the authorization allows it. this is a dummy function // that will set the content-type header to something other than application/json next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.Write([]byte("OK")) }) rr := httptest.NewRecorder() r, err := http.NewRequest(http.MethodGet, "/", nil) require.NoError(t, err) ctx := influxdbcontext.SetAuthorizer(context.Background(), mock.NewMockAuthorizer(false, tt.permList)) r = r.WithContext(ctx) h := BackupHandler{ HTTPErrorHandler: kithttp.NewErrorHandler(zaptest.NewLogger(t)), } h.requireOperPermissions(next).ServeHTTP(rr, r) rs := rr.Result() require.Equal(t, tt.wantStatus, rs.StatusCode) require.Equal(t, tt.wantContentType, rs.Header.Get("Content-Type")) }) } }