api_entities_list_test.go 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. package api
  2. import (
  3. "context"
  4. "testing"
  5. "connectrpc.com/connect"
  6. "github.com/stretchr/testify/assert"
  7. "github.com/stretchr/testify/require"
  8. apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
  9. config "github.com/OliveTin/OliveTin/internal/config"
  10. "github.com/OliveTin/OliveTin/internal/entities"
  11. "github.com/OliveTin/OliveTin/internal/executor"
  12. )
  13. func TestGetEntitiesPaginatesAndFiltersInstances(t *testing.T) {
  14. entities.ClearEntitiesOfType("server")
  15. entities.AddEntity("server", "0", map[string]any{"name": "alpha", "hostname": "alpha.example.com", "ip": "10.0.0.1"})
  16. entities.AddEntity("server", "1", map[string]any{"name": "beta", "hostname": "beta.example.com", "ip": "10.0.0.2"})
  17. entities.AddEntity("server", "2", map[string]any{"name": "gamma", "hostname": "gamma.example.com", "ip": "10.0.0.3"})
  18. t.Cleanup(func() {
  19. entities.ClearEntitiesOfType("server")
  20. })
  21. cfg := config.DefaultConfig()
  22. cfg.Entities = []*config.EntityFile{
  23. {
  24. Name: "server",
  25. Properties: []config.EntityProperty{
  26. {Name: "hostname", Title: "Hostname"},
  27. {Name: "ip", Title: "IP"},
  28. },
  29. },
  30. }
  31. cfg.Sanitize()
  32. ex := executor.DefaultExecutor(cfg)
  33. ex.RebuildActionMap()
  34. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  35. defer ts.Close()
  36. filteredResp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{
  37. EntityType: "server",
  38. Filter: "beta",
  39. Page: 1,
  40. PageSize: 10,
  41. }))
  42. require.NoError(t, err)
  43. require.Len(t, filteredResp.Msg.EntityDefinitions, 1)
  44. assert.Equal(t, int32(1), filteredResp.Msg.EntityDefinitions[0].TotalInstances)
  45. require.Len(t, filteredResp.Msg.EntityDefinitions[0].Instances, 1)
  46. assert.Equal(t, "beta.example.com", filteredResp.Msg.EntityDefinitions[0].Instances[0].Fields["hostname"])
  47. pagedResp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{
  48. EntityType: "server",
  49. Page: 2,
  50. PageSize: 1,
  51. }))
  52. require.NoError(t, err)
  53. require.Len(t, pagedResp.Msg.EntityDefinitions, 1)
  54. assert.Equal(t, int32(3), pagedResp.Msg.EntityDefinitions[0].TotalInstances)
  55. require.Len(t, pagedResp.Msg.EntityDefinitions[0].Instances, 1)
  56. assert.Equal(t, "1", pagedResp.Msg.EntityDefinitions[0].Instances[0].UniqueKey)
  57. }
  58. func TestGetEntitiesUnfilteredIncludesConfiguredProperties(t *testing.T) {
  59. entities.ClearEntitiesOfType("server")
  60. entities.AddEntity("server", "0", map[string]any{"name": "alpha", "hostname": "alpha.example.com", "ip": "10.0.0.1"})
  61. t.Cleanup(func() {
  62. entities.ClearEntitiesOfType("server")
  63. })
  64. cfg := config.DefaultConfig()
  65. cfg.Entities = []*config.EntityFile{
  66. {
  67. Name: "server",
  68. Properties: []config.EntityProperty{
  69. {Name: "hostname", Title: "Hostname"},
  70. },
  71. },
  72. }
  73. cfg.Sanitize()
  74. ex := executor.DefaultExecutor(cfg)
  75. ex.RebuildActionMap()
  76. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  77. defer ts.Close()
  78. resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{}))
  79. require.NoError(t, err)
  80. serverDef := findEntityDefinition(resp.Msg.EntityDefinitions, "server")
  81. require.NotNil(t, serverDef)
  82. require.Len(t, serverDef.Properties, 1)
  83. assert.Equal(t, "hostname", serverDef.Properties[0].Name)
  84. assert.Equal(t, int32(1), serverDef.TotalInstances)
  85. assert.Empty(t, serverDef.Instances)
  86. }
  87. func TestPaginateEntityInstancesHandlesLargePageValues(t *testing.T) {
  88. instances := []*apiv1.Entity{
  89. {UniqueKey: "0"},
  90. {UniqueKey: "1"},
  91. }
  92. assert.Empty(t, paginateEntityInstances(instances, 1<<30, 1))
  93. assert.Empty(t, paginateEntityInstances(instances, 2, 1<<30))
  94. assert.Equal(t, []*apiv1.Entity{{UniqueKey: "1"}}, paginateEntityInstances(instances, 2, 1))
  95. }
  96. func findEntityDefinition(definitions []*apiv1.EntityDefinition, title string) *apiv1.EntityDefinition {
  97. for _, definition := range definitions {
  98. if definition.Title == title {
  99. return definition
  100. }
  101. }
  102. return nil
  103. }
  104. func TestGetEntityOmitsDisplayNameFieldWhenPropertiesUnset(t *testing.T) {
  105. entities.ClearEntitiesOfType("vehicle")
  106. entities.AddEntity("vehicle", "0", map[string]any{
  107. "title": "My Car",
  108. "status": "parked",
  109. "vin": "123",
  110. })
  111. t.Cleanup(func() {
  112. entities.ClearEntitiesOfType("vehicle")
  113. })
  114. cfg := config.DefaultConfig()
  115. cfg.Entities = []*config.EntityFile{{Name: "vehicle"}}
  116. cfg.Sanitize()
  117. ex := executor.DefaultExecutor(cfg)
  118. ex.RebuildActionMap()
  119. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  120. defer ts.Close()
  121. resp, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
  122. Type: "vehicle",
  123. UniqueKey: "0",
  124. }))
  125. require.NoError(t, err)
  126. require.NotNil(t, resp.Msg)
  127. assert.Equal(t, "My Car", resp.Msg.Title)
  128. assert.Equal(t, "parked", resp.Msg.Fields["status"])
  129. assert.Equal(t, "123", resp.Msg.Fields["vin"])
  130. assert.NotContains(t, resp.Msg.Fields, "title")
  131. }
  132. func TestGetEntityRetainsNonSelectedDisplayNameCasingVariant(t *testing.T) {
  133. entities.ClearEntitiesOfType("vehicle")
  134. entities.AddEntity("vehicle", "0", map[string]any{
  135. "title": "lower-title-value",
  136. "Title": "upper-title-value",
  137. "status": "parked",
  138. })
  139. t.Cleanup(func() {
  140. entities.ClearEntitiesOfType("vehicle")
  141. })
  142. cfg := config.DefaultConfig()
  143. cfg.Entities = []*config.EntityFile{{Name: "vehicle"}}
  144. cfg.Sanitize()
  145. ex := executor.DefaultExecutor(cfg)
  146. ex.RebuildActionMap()
  147. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  148. defer ts.Close()
  149. resp, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
  150. Type: "vehicle",
  151. UniqueKey: "0",
  152. }))
  153. require.NoError(t, err)
  154. assert.Equal(t, "upper-title-value", resp.Msg.Title)
  155. assert.Equal(t, "lower-title-value", resp.Msg.Fields["title"])
  156. assert.NotContains(t, resp.Msg.Fields, "Title")
  157. assert.Equal(t, "parked", resp.Msg.Fields["status"])
  158. }
  159. func TestGetEntitiesRetainsNonSelectedDisplayNameCasingVariantInListFields(t *testing.T) {
  160. entities.ClearEntitiesOfType("vehicle")
  161. entities.AddEntity("vehicle", "0", map[string]any{
  162. "title": "lower-title-value",
  163. "Title": "upper-title-value",
  164. "status": "parked",
  165. })
  166. t.Cleanup(func() {
  167. entities.ClearEntitiesOfType("vehicle")
  168. })
  169. cfg := config.DefaultConfig()
  170. cfg.Entities = []*config.EntityFile{
  171. {
  172. Name: "vehicle",
  173. Properties: []config.EntityProperty{
  174. {Name: "title", Title: "Title"},
  175. {Name: "status", Title: "Status"},
  176. },
  177. },
  178. }
  179. cfg.Sanitize()
  180. ex := executor.DefaultExecutor(cfg)
  181. ex.RebuildActionMap()
  182. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  183. defer ts.Close()
  184. resp, err := client.GetEntities(context.Background(), connect.NewRequest(&apiv1.GetEntitiesRequest{
  185. EntityType: "vehicle",
  186. Page: 1,
  187. PageSize: 10,
  188. }))
  189. require.NoError(t, err)
  190. vehicleDef := findEntityDefinition(resp.Msg.EntityDefinitions, "vehicle")
  191. require.NotNil(t, vehicleDef)
  192. require.Len(t, vehicleDef.Instances, 1)
  193. assert.Equal(t, "upper-title-value", vehicleDef.Instances[0].Title)
  194. assert.Equal(t, "lower-title-value", vehicleDef.Instances[0].Fields["title"])
  195. assert.Equal(t, "parked", vehicleDef.Instances[0].Fields["status"])
  196. assert.NotContains(t, vehicleDef.Instances[0].Fields, "Title")
  197. }
  198. func TestGetEntityOmitsDisplayNamePropertyFromConfiguredFields(t *testing.T) {
  199. entities.ClearEntitiesOfType("vehicle")
  200. entities.AddEntity("vehicle", "0", map[string]any{
  201. "title": "My Car",
  202. "status": "parked",
  203. })
  204. t.Cleanup(func() {
  205. entities.ClearEntitiesOfType("vehicle")
  206. })
  207. cfg := config.DefaultConfig()
  208. cfg.Entities = []*config.EntityFile{
  209. {
  210. Name: "vehicle",
  211. Properties: []config.EntityProperty{
  212. {Name: "title", Title: "Title"},
  213. {Name: "status", Title: "Status"},
  214. },
  215. },
  216. }
  217. cfg.Sanitize()
  218. ex := executor.DefaultExecutor(cfg)
  219. ex.RebuildActionMap()
  220. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  221. defer ts.Close()
  222. resp, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
  223. Type: "vehicle",
  224. UniqueKey: "0",
  225. }))
  226. require.NoError(t, err)
  227. assert.Equal(t, "parked", resp.Msg.Fields["status"])
  228. assert.NotContains(t, resp.Msg.Fields, "title")
  229. }
  230. func TestGetEntityRestrictsFieldsToConfiguredProperties(t *testing.T) {
  231. entities.ClearEntitiesOfType("server")
  232. entities.AddEntity("server", "0", map[string]any{
  233. "name": "alpha",
  234. "hostname": "alpha.example.com",
  235. "ip": "10.0.0.1",
  236. "groups": []string{"admins"},
  237. })
  238. t.Cleanup(func() {
  239. entities.ClearEntitiesOfType("server")
  240. })
  241. cfg := config.DefaultConfig()
  242. cfg.Entities = []*config.EntityFile{
  243. {
  244. Name: "server",
  245. Properties: []config.EntityProperty{
  246. {Name: "hostname", Title: "Hostname"},
  247. {Name: "ip", Title: "IP"},
  248. },
  249. },
  250. }
  251. cfg.Sanitize()
  252. ex := executor.DefaultExecutor(cfg)
  253. ex.RebuildActionMap()
  254. ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
  255. defer ts.Close()
  256. resp, err := client.GetEntity(context.Background(), connect.NewRequest(&apiv1.GetEntityRequest{
  257. Type: "server",
  258. UniqueKey: "0",
  259. }))
  260. require.NoError(t, err)
  261. require.NotNil(t, resp.Msg)
  262. assert.Equal(t, "alpha.example.com", resp.Msg.Fields["hostname"])
  263. assert.Equal(t, "10.0.0.1", resp.Msg.Fields["ip"])
  264. assert.NotContains(t, resp.Msg.Fields, "groups")
  265. assert.NotContains(t, resp.Msg.Fields, "name")
  266. }