github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/jamf_test.go (about)

     1  // Copyright 2023 Gravitational, Inc
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package types_test
    16  
    17  import (
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/gogo/protobuf/proto"
    22  	"github.com/gravitational/trace"
    23  	"github.com/stretchr/testify/assert"
    24  
    25  	"github.com/gravitational/teleport/api/types"
    26  )
    27  
    28  func TestValidateJamfSpecV1(t *testing.T) {
    29  	validSpec := &types.JamfSpecV1{
    30  		Enabled:     true,
    31  		ApiEndpoint: "https://yourtenant.jamfcloud.com",
    32  		Username:    "llama",
    33  		Password:    "supersecret!!1!",
    34  	}
    35  	validEntry := &types.JamfInventoryEntry{
    36  		FilterRsql:        "", // no filters
    37  		SyncPeriodPartial: 0,  // default period
    38  		SyncPeriodFull:    0,  // default period
    39  		OnMissing:         "", // same as NOOP
    40  	}
    41  
    42  	modify := func(f func(spec *types.JamfSpecV1)) *types.JamfSpecV1 {
    43  		spec := proto.Clone(validSpec).(*types.JamfSpecV1)
    44  		f(spec)
    45  		return spec
    46  	}
    47  
    48  	tests := []struct {
    49  		name    string
    50  		spec    *types.JamfSpecV1
    51  		wantErr string
    52  	}{
    53  		{
    54  			name: "minimal spec",
    55  			spec: validSpec,
    56  		},
    57  		{
    58  			name: "spec with inventory",
    59  			spec: &types.JamfSpecV1{
    60  				Enabled:     true,
    61  				ApiEndpoint: "https://yourtenant.jamfcloud.com",
    62  				Username:    "llama",
    63  				Password:    "supersecret!!1!",
    64  				Inventory: []*types.JamfInventoryEntry{
    65  					{
    66  						FilterRsql:        `general.remoteManagement.managed==true and general.platform=="Mac"`,
    67  						SyncPeriodPartial: types.Duration(4 * time.Hour),
    68  						SyncPeriodFull:    types.Duration(48 * time.Hour),
    69  						OnMissing:         "DELETE",
    70  					},
    71  					{
    72  						FilterRsql: `general.remoteManagement.managed==false`,
    73  						OnMissing:  "NOOP",
    74  					},
    75  					validEntry,
    76  				},
    77  			},
    78  		},
    79  		{
    80  			name: "all fields",
    81  			spec: &types.JamfSpecV1{
    82  				Enabled:     true,
    83  				Name:        "jamf2",
    84  				SyncDelay:   types.Duration(2 * time.Minute),
    85  				ApiEndpoint: "https://yourtenant.jamfcloud.com",
    86  				Username:    "llama",
    87  				Password:    "supersecret!!1!",
    88  				Inventory: []*types.JamfInventoryEntry{
    89  					{
    90  						FilterRsql:        `general.remoteManagement.managed==true and general.platform=="Mac"`,
    91  						SyncPeriodPartial: types.Duration(4 * time.Hour),
    92  						SyncPeriodFull:    types.Duration(48 * time.Hour),
    93  						OnMissing:         "DELETE",
    94  					},
    95  				},
    96  			},
    97  		},
    98  		{
    99  			name:    "nil spec",
   100  			spec:    nil,
   101  			wantErr: "spec required",
   102  		},
   103  		{
   104  			name: "api_endpoint invalid",
   105  			spec: modify(func(spec *types.JamfSpecV1) {
   106  				spec.ApiEndpoint = "https://%%"
   107  			}),
   108  			wantErr: "API endpoint",
   109  		},
   110  		{
   111  			name: "api_endpoint empty hostname",
   112  			spec: modify(func(spec *types.JamfSpecV1) {
   113  				spec.ApiEndpoint = "not a valid URL"
   114  			}),
   115  			wantErr: "missing hostname",
   116  		},
   117  		{
   118  			name: "username empty",
   119  			spec: modify(func(spec *types.JamfSpecV1) {
   120  				spec.Username = ""
   121  			}),
   122  			wantErr: "username",
   123  		},
   124  		{
   125  			name: "password empty",
   126  			spec: modify(func(spec *types.JamfSpecV1) {
   127  				spec.Password = ""
   128  			}),
   129  			wantErr: "password",
   130  		},
   131  		{
   132  			name: "inventory nil entry",
   133  			spec: modify(func(spec *types.JamfSpecV1) {
   134  				spec.Inventory = []*types.JamfInventoryEntry{
   135  					nil,
   136  				}
   137  			}),
   138  			wantErr: "is nil",
   139  		},
   140  		{
   141  			name: "inventory sync_partial > sync_full",
   142  			spec: modify(func(spec *types.JamfSpecV1) {
   143  				spec.Inventory = []*types.JamfInventoryEntry{
   144  					validEntry,
   145  					{
   146  						SyncPeriodPartial: types.Duration(12 * time.Hour),
   147  						SyncPeriodFull:    types.Duration(8 * time.Hour),
   148  					},
   149  				}
   150  			}),
   151  			wantErr: "greater or equal to sync_period_full",
   152  		},
   153  		{
   154  			name: "inventory on_missing invalid",
   155  			spec: modify(func(spec *types.JamfSpecV1) {
   156  				spec.Inventory = []*types.JamfInventoryEntry{
   157  					validEntry,
   158  					{
   159  						OnMissing: "BANANA",
   160  					},
   161  				}
   162  			}),
   163  			wantErr: "on_missing",
   164  		},
   165  		{
   166  			name: "inventory sync_partial disabled",
   167  			spec: modify(func(spec *types.JamfSpecV1) {
   168  				spec.Inventory = []*types.JamfInventoryEntry{
   169  					validEntry,
   170  					{
   171  						SyncPeriodPartial: -1,
   172  						SyncPeriodFull:    types.Duration(8 * time.Hour),
   173  					},
   174  				}
   175  			}),
   176  		},
   177  		{
   178  			name: "inventory sync_full disabled",
   179  			spec: modify(func(spec *types.JamfSpecV1) {
   180  				spec.Inventory = []*types.JamfInventoryEntry{
   181  					validEntry,
   182  					{
   183  						SyncPeriodPartial: types.Duration(12 * time.Hour),
   184  						SyncPeriodFull:    -1,
   185  					},
   186  				}
   187  			}),
   188  		},
   189  		{
   190  			name: "inventory all syncs disabled",
   191  			spec: modify(func(spec *types.JamfSpecV1) {
   192  				spec.Inventory = []*types.JamfInventoryEntry{
   193  					validEntry,
   194  					{
   195  						SyncPeriodPartial: 0,
   196  						SyncPeriodFull:    0,
   197  					},
   198  				}
   199  			}),
   200  		},
   201  	}
   202  	for _, test := range tests {
   203  		t.Run(test.name, func(t *testing.T) {
   204  			err := types.ValidateJamfSpecV1(test.spec)
   205  			if test.wantErr == "" {
   206  				assert.NoError(t, err, "ValidateJamfSpecV1 failed")
   207  			} else {
   208  				assert.ErrorContains(t, err, test.wantErr, "ValidateJamfSpecV1 error mismatch")
   209  				assert.True(t, trace.IsBadParameter(err), "ValidateJamfSpecV1 returned non-BadParameter error: %T", err)
   210  			}
   211  		})
   212  	}
   213  }