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 }