fake.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /*
  2. Copyright © The ESO Authors
  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. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package fake
  14. import (
  15. "context"
  16. "fmt"
  17. "maps"
  18. "math/rand"
  19. "net/http"
  20. "slices"
  21. "strings"
  22. "sync"
  23. "time"
  24. "github.com/ngrok/ngrok-api-go/v7"
  25. )
  26. func GenerateRandomString(length int) string {
  27. const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  28. seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) /* #nosec G404 */
  29. sb := strings.Builder{}
  30. sb.Grow(length)
  31. for range length {
  32. sb.WriteByte(charset[seededRand.Intn(len(charset))])
  33. }
  34. return sb.String()
  35. }
  36. func VaultNameEmpty() *ngrok.Error {
  37. return &ngrok.Error{
  38. ErrorCode: "ERR_NGROK_23001",
  39. StatusCode: http.StatusBadRequest,
  40. Msg: "The vault name cannot be empty.",
  41. }
  42. }
  43. func VaultNamesMustBeUniqueWithinAccount() *ngrok.Error {
  44. return &ngrok.Error{
  45. ErrorCode: "ERR_NGROK_23004",
  46. StatusCode: http.StatusBadRequest,
  47. Msg: "Vault names must be unique within an account.",
  48. }
  49. }
  50. func VaultNameInvalid(name string) *ngrok.Error {
  51. return &ngrok.Error{
  52. ErrorCode: "ERR_NGROK_23002",
  53. StatusCode: http.StatusBadRequest,
  54. Msg: fmt.Sprintf("The vault name %q is invalid. Must only contain the characters \"a-zA-Z0-9_/.\".", name),
  55. }
  56. }
  57. func SecretNameEmpty() *ngrok.Error {
  58. return &ngrok.Error{
  59. ErrorCode: "ERR_NGROK_24001",
  60. StatusCode: http.StatusBadRequest,
  61. Msg: "The secret name cannot be empty.",
  62. }
  63. }
  64. func SecretValueEmpty() *ngrok.Error {
  65. return &ngrok.Error{
  66. ErrorCode: "ERR_NGROK_24003",
  67. StatusCode: http.StatusBadRequest,
  68. Msg: "The secret value cannot be empty.",
  69. }
  70. }
  71. func SecretNameMustBeUniqueWithinVault() *ngrok.Error {
  72. return &ngrok.Error{
  73. ErrorCode: "ERR_NGROK_24005",
  74. StatusCode: http.StatusBadRequest,
  75. Msg: "Secret names must be unique within a vault.",
  76. }
  77. }
  78. func SecretVaultNotFound(id string) *ngrok.Error {
  79. return &ngrok.Error{
  80. ErrorCode: "ERR_NGROK_24006",
  81. StatusCode: http.StatusNotFound,
  82. Msg: fmt.Sprintf("Vault with ID %s not found.", id),
  83. }
  84. }
  85. func NotFound(id string) *ngrok.Error {
  86. return &ngrok.Error{
  87. StatusCode: http.StatusNotFound,
  88. Msg: fmt.Sprintf("Resource with ID %s not found.", id),
  89. }
  90. }
  91. func VaultNotEmpty() *ngrok.Error {
  92. return &ngrok.Error{
  93. ErrorCode: "ERR_NGROK_23003",
  94. StatusCode: http.StatusBadRequest,
  95. Msg: "A Vault must be empty before it can be deleted. Please remove all secrets from the vault and try again.",
  96. }
  97. }
  98. type vault struct {
  99. vault *ngrok.Vault
  100. mu sync.RWMutex
  101. secretsByID map[string]*ngrok.Secret
  102. }
  103. // newVault creates a new vault instance with an empty secrets map.
  104. // given the ngrok.Vault to wrap.
  105. func newVault(v *ngrok.Vault) *vault {
  106. return &vault{
  107. vault: v,
  108. secretsByID: make(map[string]*ngrok.Secret),
  109. }
  110. }
  111. func (v *vault) setSecret(id string, secret *ngrok.Secret) {
  112. v.mu.Lock()
  113. defer v.mu.Unlock()
  114. v.secretsByID[id] = secret
  115. }
  116. func (v *vault) getSecret(id string) (*ngrok.Secret, bool) {
  117. v.mu.RLock()
  118. defer v.mu.RUnlock()
  119. val, ok := v.secretsByID[id]
  120. return val, ok
  121. }
  122. func (v *vault) deleteSecret(id string) {
  123. v.mu.Lock()
  124. defer v.mu.Unlock()
  125. delete(v.secretsByID, id)
  126. }
  127. // CreateSecret creates a new secret in the vault.
  128. func (v *vault) CreateSecret(s *ngrok.SecretCreate) (*ngrok.Secret, error) {
  129. if s.Name == "" {
  130. return nil, SecretNameEmpty()
  131. }
  132. if s.Value == "" {
  133. return nil, SecretValueEmpty()
  134. }
  135. existing := v.GetSecretByName(s.Name)
  136. if existing != nil {
  137. return nil, SecretNameMustBeUniqueWithinVault()
  138. }
  139. ts := time.Now()
  140. newSecret := &ngrok.Secret{
  141. ID: "secret_" + GenerateRandomString(20),
  142. Vault: ngrok.Ref{
  143. ID: v.vault.ID,
  144. URI: v.vault.URI,
  145. },
  146. Name: s.Name,
  147. Description: s.Description,
  148. Metadata: s.Metadata,
  149. CreatedAt: ts.Format(time.RFC3339),
  150. UpdatedAt: ts.Format(time.RFC3339),
  151. }
  152. v.setSecret(newSecret.ID, newSecret)
  153. return newSecret, nil
  154. }
  155. // DeleteSecret deletes a secret from the vault by ID.
  156. func (v *vault) DeleteSecret(id string) error {
  157. _, exists := v.getSecret(id)
  158. if exists {
  159. v.deleteSecret(id)
  160. return nil
  161. }
  162. return NotFound(id)
  163. }
  164. // ListSecrets returns all secrets in the vault.
  165. func (v *vault) ListSecrets() []*ngrok.Secret {
  166. v.mu.RLock()
  167. defer v.mu.RUnlock()
  168. return slices.Collect(maps.Values(v.secretsByID))
  169. }
  170. // GetSecretByID returns the secret with the given ID, or nil if not found.
  171. func (v *vault) GetSecretByID(id string) *ngrok.Secret {
  172. val, _ := v.getSecret(id)
  173. return val
  174. }
  175. // GetSecretByName returns the secret with the given name, or nil if not found.
  176. func (v *vault) GetSecretByName(name string) *ngrok.Secret {
  177. for _, secret := range v.ListSecrets() {
  178. if secret.Name == name {
  179. return secret
  180. }
  181. }
  182. return nil
  183. }
  184. // UpdateSecret updates an existing secret in the vault.
  185. func (v *vault) UpdateSecret(s *ngrok.SecretUpdate) (*ngrok.Secret, error) {
  186. secret := v.GetSecretByID(s.ID)
  187. if secret == nil {
  188. return nil, NotFound(s.ID)
  189. }
  190. if s.Name != nil {
  191. if *s.Name == "" {
  192. return nil, SecretNameEmpty()
  193. }
  194. existing := v.GetSecretByName(*s.Name)
  195. if existing != nil && existing.ID != s.ID {
  196. return nil, SecretNameMustBeUniqueWithinVault()
  197. }
  198. }
  199. if s.Value != nil {
  200. if *s.Value == "" {
  201. return nil, SecretValueEmpty()
  202. }
  203. }
  204. ts := time.Now()
  205. secret.UpdatedAt = ts.Format(time.RFC3339)
  206. if s.Name != nil {
  207. secret.Name = *s.Name
  208. }
  209. if s.Description != nil {
  210. secret.Description = *s.Description
  211. }
  212. if s.Metadata != nil {
  213. secret.Metadata = *s.Metadata
  214. }
  215. return secret, nil
  216. }
  217. type Store struct {
  218. mu sync.RWMutex
  219. vaultsByID map[string]*vault
  220. }
  221. func NewStore() *Store {
  222. return &Store{
  223. vaultsByID: make(map[string]*vault),
  224. }
  225. }
  226. func (s *Store) setVault(id string, v *vault) {
  227. s.mu.Lock()
  228. defer s.mu.Unlock()
  229. s.vaultsByID[id] = v
  230. }
  231. func (s *Store) getVault(id string) (*vault, bool) {
  232. s.mu.RLock()
  233. defer s.mu.RUnlock()
  234. val, ok := s.vaultsByID[id]
  235. return val, ok
  236. }
  237. func (s *Store) deleteVault(id string) {
  238. s.mu.Lock()
  239. defer s.mu.Unlock()
  240. delete(s.vaultsByID, id)
  241. }
  242. // CreateVault creates a new vault in the store.
  243. func (s *Store) CreateVault(v *ngrok.VaultCreate) (*ngrok.Vault, error) {
  244. if v.Name == "" {
  245. return nil, VaultNameEmpty()
  246. }
  247. for _, vault := range s.ListVaults() {
  248. if vault.Name == v.Name {
  249. return nil, VaultNamesMustBeUniqueWithinAccount()
  250. }
  251. }
  252. ts := time.Now()
  253. ngrokVault := &ngrok.Vault{
  254. ID: "vault_" + GenerateRandomString(20),
  255. Name: v.Name,
  256. Description: v.Description,
  257. Metadata: v.Metadata,
  258. CreatedAt: ts.Format(time.RFC3339),
  259. UpdatedAt: ts.Format(time.RFC3339),
  260. }
  261. s.setVault(ngrokVault.ID, newVault(ngrokVault))
  262. return ngrokVault, nil
  263. }
  264. func (s *Store) CreateSecret(secret *ngrok.SecretCreate) (*ngrok.Secret, error) {
  265. v, _ := s.getVault(secret.VaultID)
  266. if v == nil {
  267. return nil, SecretVaultNotFound(secret.VaultID)
  268. }
  269. return v.CreateSecret(secret)
  270. }
  271. // DeleteVault deletes a vault from the store by ID.
  272. func (s *Store) DeleteVault(id string) error {
  273. v, _ := s.getVault(id)
  274. if v == nil {
  275. return NotFound(id)
  276. }
  277. if len(v.ListSecrets()) > 0 {
  278. return VaultNotEmpty()
  279. }
  280. s.deleteVault(id)
  281. return nil
  282. }
  283. // GetVaultByID returns the vault with the given ID, or nil if not found.
  284. func (s *Store) GetVaultByID(id string) (*ngrok.Vault, error) {
  285. v, _ := s.getVault(id)
  286. if v == nil {
  287. return nil, NotFound(id)
  288. }
  289. return v.vault, nil
  290. }
  291. func (s *Store) GetVaultByName(name string) *ngrok.Vault {
  292. for _, v := range s.ListVaults() {
  293. if v.Name == name {
  294. return v
  295. }
  296. }
  297. return nil
  298. }
  299. // ListSecrets returns all secrets in the store.
  300. func (s *Store) ListSecrets() []*ngrok.Secret {
  301. s.mu.RLock()
  302. defer s.mu.RUnlock()
  303. secrets := []*ngrok.Secret{}
  304. for _, v := range s.vaultsByID {
  305. secrets = append(secrets, v.ListSecrets()...)
  306. }
  307. return secrets
  308. }
  309. // ListVaults returns all vaults in the store.
  310. func (s *Store) ListVaults() []*ngrok.Vault {
  311. s.mu.RLock()
  312. defer s.mu.RUnlock()
  313. vaults := make([]*ngrok.Vault, 0, len(s.vaultsByID))
  314. for _, v := range s.vaultsByID {
  315. vaults = append(vaults, v.vault)
  316. }
  317. return vaults
  318. }
  319. func (s *Store) ListVaultSecrets(vaultID string) ([]*ngrok.Secret, error) {
  320. v, _ := s.getVault(vaultID)
  321. if v == nil {
  322. return nil, NotFound(vaultID)
  323. }
  324. return v.ListSecrets(), nil
  325. }
  326. func (s *Store) UpdateSecret(secret *ngrok.SecretUpdate) (*ngrok.Secret, error) {
  327. var found *ngrok.Secret
  328. for _, sec := range s.ListSecrets() {
  329. if sec.ID == secret.ID {
  330. found = sec
  331. break
  332. }
  333. }
  334. if found == nil {
  335. return nil, NotFound(secret.ID)
  336. }
  337. v, ok := s.getVault(found.Vault.ID)
  338. if !ok {
  339. return nil, SecretVaultNotFound(found.Vault.ID)
  340. }
  341. return v.UpdateSecret(secret)
  342. }
  343. func (s *Store) DeleteSecret(secretID string) error {
  344. secret, vault, err := s.GetSecretAndVaultByID(secretID)
  345. if err != nil {
  346. return err
  347. }
  348. if secret == nil || vault == nil {
  349. return NotFound(secretID)
  350. }
  351. return vault.DeleteSecret(secretID)
  352. }
  353. func (s *Store) GetSecretAndVaultByID(secretID string) (*ngrok.Secret, *vault, error) {
  354. s.mu.RLock()
  355. defer s.mu.RUnlock()
  356. for _, v := range s.vaultsByID {
  357. if sec := v.GetSecretByID(secretID); sec != nil {
  358. return sec, v, nil
  359. }
  360. }
  361. return nil, nil, NotFound(secretID)
  362. }
  363. func (s *Store) VaultClient() *VaultClient {
  364. return &VaultClient{
  365. store: s,
  366. }
  367. }
  368. func (s *Store) SecretsClient() *SecretsClient {
  369. return &SecretsClient{
  370. store: s,
  371. }
  372. }
  373. // VaultClient is a mock implementation which implements the ngrok.VaultsClient interface.
  374. type VaultClient struct {
  375. store *Store
  376. createErr error
  377. listErr error
  378. }
  379. // WithCreateError sets an error to be returned when Create is called.
  380. // This is useful for testing error handling in the client.
  381. func (m *VaultClient) WithCreateError(err error) *VaultClient {
  382. m.createErr = err
  383. return m
  384. }
  385. // Create creates a new vault and returns it. If an error is set, it will return that error instead of the vault.
  386. func (m *VaultClient) Create(_ context.Context, vault *ngrok.VaultCreate) (*ngrok.Vault, error) {
  387. if m.createErr != nil {
  388. return nil, m.createErr
  389. }
  390. return m.store.CreateVault(vault)
  391. }
  392. // Get retrieves a vault by its ID. If the vault does not exist, it returns an error.
  393. func (m *VaultClient) Get(_ context.Context, vaultID string) (*ngrok.Vault, error) {
  394. return m.store.GetVaultByID(vaultID)
  395. }
  396. func (m *VaultClient) GetSecretsByVault(id string, paging *ngrok.Paging) ngrok.Iter[*ngrok.Secret] {
  397. secrets, err := m.store.ListVaultSecrets(id)
  398. return NewIter(secrets, err)
  399. }
  400. // WithListError sets an error to be returned when List is called.
  401. func (m *VaultClient) WithListError(err error) *VaultClient {
  402. m.listErr = err
  403. return m
  404. }
  405. // List returns an iterator over the vaults.
  406. // If an error is set, it will return that error instead of the vaults.
  407. func (m *VaultClient) List(paging *ngrok.Paging) ngrok.Iter[*ngrok.Vault] {
  408. return NewIter(m.store.ListVaults(), m.listErr)
  409. }
  410. // SecretsClient is a mock implementation of the SecretsClient interface.
  411. // It allows you to create, update, delete, and list secrets.
  412. // It can be used to test the client without needing a real ngrok API.
  413. type SecretsClient struct {
  414. store *Store
  415. createErr error
  416. updateErr error
  417. deleteErr error
  418. listErr error
  419. }
  420. // WithCreateError sets an error to be returned when Create is called.
  421. // This is useful for testing error handling in the client.
  422. func (m *SecretsClient) WithCreateError(err error) *SecretsClient {
  423. m.createErr = err
  424. return m
  425. }
  426. // Create creates a new secret and returns it. If an error is set, it will return that error instead of the secret.
  427. func (m *SecretsClient) Create(_ context.Context, secret *ngrok.SecretCreate) (*ngrok.Secret, error) {
  428. if m.createErr != nil {
  429. return nil, m.createErr
  430. }
  431. return m.store.CreateSecret(secret)
  432. }
  433. // WithUpdateError sets an error to be returned when Update is called.
  434. // This is useful for testing error handling in the client.
  435. func (m *SecretsClient) WithUpdateError(err error) *SecretsClient {
  436. m.updateErr = err
  437. return m
  438. }
  439. // Update updates an existing secret and returns it. If an error is set, it will return that error instead of the secret.
  440. func (m *SecretsClient) Update(_ context.Context, secret *ngrok.SecretUpdate) (*ngrok.Secret, error) {
  441. if m.updateErr != nil {
  442. return nil, m.updateErr
  443. }
  444. return m.store.UpdateSecret(secret)
  445. }
  446. // WithDeleteError sets an error to be returned when Delete is called.
  447. // This is useful for testing error handling in the client.
  448. func (m *SecretsClient) WithDeleteError(err error) *SecretsClient {
  449. m.deleteErr = err
  450. return m
  451. }
  452. // Delete deletes a secret by its ID. If an error is set, it will return that error instead of deleting the secret.
  453. // If the secret does not exist, it returns an error.
  454. func (m *SecretsClient) Delete(_ context.Context, secretID string) error {
  455. if m.deleteErr != nil {
  456. return m.deleteErr
  457. }
  458. return m.store.DeleteSecret(secretID)
  459. }
  460. // Get retrieves a secret by its ID. If the secret does not exist, it returns an error.
  461. func (m *SecretsClient) Get(_ context.Context, secretID string) (*ngrok.Secret, error) {
  462. s, _, err := m.store.GetSecretAndVaultByID(secretID) // to check existence
  463. return s, err
  464. }
  465. // WithListError sets an error to be returned when List is called.
  466. // This is useful for testing error handling in the client.
  467. func (m *SecretsClient) WithListError(err error) *SecretsClient {
  468. m.listErr = err
  469. return m
  470. }
  471. // List returns an iterator over the secrets.
  472. // If an error is set, it will return that error instead of the secrets.
  473. func (m *SecretsClient) List(paging *ngrok.Paging) ngrok.Iter[*ngrok.Secret] {
  474. return NewIter(m.store.ListSecrets(), m.listErr)
  475. }
  476. // Iter is a mock iterator that implements the ngrok.Iter[T] interface.
  477. type Iter[T any] struct {
  478. items []T
  479. err error
  480. n int
  481. }
  482. func (m *Iter[T]) Next(_ context.Context) bool {
  483. // If there is an error, stop iteration
  484. if m.err != nil {
  485. return false
  486. }
  487. // Increment the index
  488. m.n++
  489. return m.n < len(m.items) && m.n >= 0
  490. }
  491. func (m *Iter[T]) Item() T {
  492. if m.n >= 0 && m.n < len(m.items) {
  493. return m.items[m.n]
  494. }
  495. return *new(T)
  496. }
  497. func (m *Iter[T]) Err() error {
  498. return m.err
  499. }
  500. func NewIter[T any](items []T, err error) *Iter[T] {
  501. return &Iter[T]{
  502. items: items,
  503. err: err,
  504. n: -1,
  505. }
  506. }