package rulestest import ( "net/http" "strings" "testing" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" _ "github.com/jackc/pgx/v4/stdlib" "edge-infra.dev/test/f2" "edge-infra.dev/test/f2/x/postgres" ) const bannerData = ` INSERT INTO ea_rules_commands (command_id, name) VALUES ('78587bb1-6ca2-4d2d-a223-1ee642514b97', 'ls'), ('eb6fc295-a46c-4407-a7a3-1438436d6aee', 'cat') ; INSERT INTO banners (banner_edge_id, banner_name) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', 'myBanner'), ('8880dd52-f152-44ac-ac76-b37dd3a3a2f2', 'myBanner2') ; INSERT INTO ea_rules_privileges (privilege_id, name) VALUES ('a7c379ea-6e34-4017-8e86-eb545d7856a3', 'ea-read'), ('caedabee-ea7a-4421-a608-ec04106e61da', 'ea-write') ; ` func TestAdminBannerRules(t *testing.T) { var ( rulesEngine *gin.Engine ) feat := f2.NewFeature("Admin Banner Rules"). Setup("Create Rules Engine server", func(ctx f2.Context, t *testing.T) f2.Context { var db = postgres.FromContextT(ctx, t).DB() rulesEngine, _ = setupRulesEngine(t, db) return ctx }). Setup("Populate Seed Data", func(ctx f2.Context, t *testing.T) f2.Context { // Seed data contains banner, command and privilege definitions, without // defining any rules var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, bannerData) require.NoError(t, err) return ctx }). Test("Get No Rules for Banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `null`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Get All Rules for non-existent banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=nonExistentBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `null`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Get All rules for Banner", func(ctx f2.Context, t *testing.T) f2.Context { // Add a rule with single privilege to db and confirm correct result var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `[ { "command": { "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97", "name": "ls" }, "privileges": [{ "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3", "name": "ea-read" }] } ]`, } ctx = testEndpoint(ctx, t, rulesEngine, test) // Add a privilege to existing rule and create new rule _, err = db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da'), ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', 'eb6fc295-a46c-4407-a7a3-1438436d6aee', 'caedabee-ea7a-4421-a608-ec04106e61da') ;`, ) assert.NoError(t, err) test.expectedOut = `[ { "command": { "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97", "name": "ls" }, "privileges": [{ "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3", "name": "ea-read" }, { "id": "caedabee-ea7a-4421-a608-ec04106e61da", "name": "ea-write" } ] }, { "command": { "id": "eb6fc295-a46c-4407-a7a3-1438436d6aee", "name": "cat" }, "privileges": [{ "id": "caedabee-ea7a-4421-a608-ec04106e61da", "name": "ea-write" }] } ]` ctx = testEndpoint(ctx, t, rulesEngine, test) _, err = db.ExecContext(ctx, `DELETE from ea_rules;`) assert.NoError(t, err) return ctx }). Test("Get All Rules For All Banners", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('8880dd52-f152-44ac-ac76-b37dd3a3a2f2', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3'), ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `[ { "command": { "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97", "name": "ls" }, "banners": [ { "banner": { "id": "8880dd52-f152-44ac-ac76-b37dd3a3a2f2", "name": "myBanner2" }, "privileges": [ { "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3", "name":"ea-read" } ] }, { "banner": { "id": "2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a", "name": "myBanner" }, "privileges": [ { "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3", "name": "ea-read" } ] } ] } ]`, } ctx = testEndpoint(ctx, t, rulesEngine, test) _, err = db.ExecContext(ctx, `DELETE from ea_rules;`) assert.NoError(t, err) return ctx }). Test("Delete Unknown Privilege From Rule in Banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands/rm/privileges/read?bannerName=unknownBanner", method: http.MethodDelete, expectedStatus: http.StatusNotFound, expectedOut: `{ "errors": [ { "type": "Unknown Command", "command":"rm" }, { "type":"Unknown Privilege", "privilege":"read" }, { "type":"Unknown Banner", "banner":"unknownBanner" } ] }`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Delete Known Privilege From No Rule in Banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands/ls/privileges/ea-read?bannerName=myBanner", method: http.MethodDelete, expectedStatus: http.StatusNotFound, expectedOut: `{ "errors": [{ "type": "Unknown Rule association", "banner": "myBanner", "command": "ls", "privilege": "ea-read" }] }`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Delete Known Privilege From Rule in Banner", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands/ls/privileges/ea-read?bannerName=myBanner", method: http.MethodDelete, expectedStatus: http.StatusOK, expectedOut: ``, } ctx = testEndpoint(ctx, t, rulesEngine, test) rows, err := db.QueryContext(ctx, `SELECT * FROM ea_rules`) assert.NoError(t, err) defer rows.Close() for rows.Next() { assert.Fail(t, "Expected no rows") } assert.NoError(t, rows.Err()) return ctx }). Test("Delete Single Privilege From Rule in Banner", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3'), ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands/ls/privileges/ea-read?bannerName=myBanner", method: http.MethodDelete, expectedStatus: http.StatusOK, expectedOut: ``, } ctx = testEndpoint(ctx, t, rulesEngine, test) rows, err := db.QueryContext(ctx, `SELECT banner_edge_id,command_id,privilege_id FROM ea_rules`) assert.NoError(t, err) defer rows.Close() var rowCount int for rows.Next() { var bannerID, commandID, privilegeID string rowCount++ err = rows.Scan(&bannerID, &commandID, &privilegeID) assert.NoError(t, err) assert.Equal(t, "2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a", bannerID) assert.Equal(t, "78587bb1-6ca2-4d2d-a223-1ee642514b97", commandID) assert.Equal(t, "caedabee-ea7a-4421-a608-ec04106e61da", privilegeID) } assert.NoError(t, rows.Err()) assert.Equal(t, 1, rowCount) _, err = db.ExecContext(ctx, `DELETE from ea_rules;`) assert.NoError(t, err) return ctx }). Test("Get Banner Rules for Command", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3'), ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da'), ('8880dd52-f152-44ac-ac76-b37dd3a3a2f2', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands/ls", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `{ "command": { "name": "ls", "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97" }, "banners": [ { "banner": { "name": "myBanner", "id": "2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a" }, "privileges": [ { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" }, { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" } ] }, { "banner": { "name": "myBanner2", "id": "8880dd52-f152-44ac-ac76-b37dd3a3a2f2" }, "privileges": [ { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" } ] } ] }`, } ctx = testEndpoint(ctx, t, rulesEngine, test) _, err = db.ExecContext(ctx, `DELETE from ea_rules;`) assert.NoError(t, err) return ctx }). Test("Get No Banner Rules for Command", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands/ls", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `null`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Get Banner Rules for Command filtered to single banner", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, ` INSERT INTO ea_rules (banner_edge_id, command_id, privilege_id) VALUES ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'a7c379ea-6e34-4017-8e86-eb545d7856a3'), ('2f9f5965-ed2a-4262-9fd9-9d2d8f8bee8a', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da'), ('8880dd52-f152-44ac-ac76-b37dd3a3a2f2', '78587bb1-6ca2-4d2d-a223-1ee642514b97', 'caedabee-ea7a-4421-a608-ec04106e61da') ;`, ) assert.NoError(t, err) test := testCase{ url: "/admin/rules/banner/commands/ls?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `{ "command": { "name": "ls", "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97" }, "privileges": [ { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" }, { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" } ] }`, } ctx = testEndpoint(ctx, t, rulesEngine, test) _, err = db.ExecContext(ctx, `DELETE from ea_rules;`) assert.NoError(t, err) return ctx }). Test("Get No Banner Rules for Command filtered to single banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands/ls?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `null`, } return testEndpoint(ctx, t, rulesEngine, test) }). Feature() // Run the tests f.Test(t, feat) } func TestPostBannerRules(t *testing.T) { var ( rulesEngine *gin.Engine ) feat := f2.NewFeature("Admin POST Banner Rules"). Setup("Create Rules Engine server", func(ctx f2.Context, t *testing.T) f2.Context { var db = postgres.FromContextT(ctx, t).DB() rulesEngine, _ = setupRulesEngine(t, db) return ctx }). Setup("Populate Seed Data", func(ctx f2.Context, t *testing.T) f2.Context { // Seed data contains banner, command and privilege definitions, without // defining any rules var ( db = postgres.FromContextT(ctx, t).DB() ) _, err := db.ExecContext(ctx, bannerData) require.NoError(t, err) return ctx }). Test("POST to invalid Banner", func(ctx f2.Context, t *testing.T) f2.Context { var ( db = postgres.FromContextT(ctx, t).DB() ) test := testCase{ url: "/admin/rules/banner/commands?bannerName=anUnknownBanner", method: http.MethodPost, expectedStatus: http.StatusNotFound, body: strings.NewReader(`[ { "command": "ls", "privileges": ["ea-read"] }, { "command": "cat", "privileges": ["ea-write"] }, { "command": "unknownCommand", "privileges": ["unknownPrivilege"] } ]`), expectedOut: `{ "errors": [ {"banner": "anUnknownBanner", "type": "Unknown Banner"}, {"banner": "anUnknownBanner", "type": "Unknown Banner"}, {"banner": "anUnknownBanner", "type": "Unknown Banner"}, {"command": "unknownCommand", "type": "Unknown Command"}, {"privilege": "unknownPrivilege", "type": "Unknown Privilege"} ] }`, } ctx = testEndpoint(ctx, t, rulesEngine, test) rows, err := db.QueryContext(ctx, `SELECT * FROM ea_rules`) assert.NoError(t, err) defer rows.Close() for rows.Next() { assert.Fail(t, "Expected no rows") } assert.NoError(t, rows.Err()) return ctx }). Test("POST two Rules to Banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodPost, expectedStatus: http.StatusOK, body: strings.NewReader(`[ { "command": "ls", "privileges": ["ea-read"] }, { "command": "cat", "privileges": ["ea-write"] } ]`), } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Confirm Expected State", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `[ { "command": { "name": "ls", "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97" }, "privileges": [ { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" } ] }, { "command": { "name": "cat", "id": "eb6fc295-a46c-4407-a7a3-1438436d6aee" }, "privileges": [ { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" } ] } ]`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("POST invalid Rule to Banner", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodPost, expectedStatus: http.StatusNotFound, body: strings.NewReader(`[ { "command": "unknownCommand", "privileges": ["ea-read"] }, { "command": "cat", "privileges": ["ea-read"] } ]`), expectedOut: `{ "errors": [ { "command": "unknownCommand", "type": "Unknown Command" } ] }`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Confirm Expected State", func(ctx f2.Context, t *testing.T) f2.Context { test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, // State should be identical to before running the POST with invalid data expectedOut: `[ { "command": { "name": "ls", "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97" }, "privileges": [ { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" } ] }, { "command": { "name": "cat", "id": "eb6fc295-a46c-4407-a7a3-1438436d6aee" }, "privileges": [ { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" } ] } ]`, } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Post duplicated Rule to Banner", func(ctx f2.Context, t *testing.T) f2.Context { // Duplicated rule should be ignored, while new rule added test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodPost, expectedStatus: http.StatusOK, body: strings.NewReader(`[ { "command": "ls", "privileges": ["ea-read"] }, { "command": "cat", "privileges": ["ea-read"] } ]`), } return testEndpoint(ctx, t, rulesEngine, test) }). Test("Confirm Expected State", func(ctx f2.Context, t *testing.T) f2.Context { // Should include single extra rule from previously test := testCase{ url: "/admin/rules/banner/commands?bannerName=myBanner", method: http.MethodGet, expectedStatus: http.StatusOK, expectedOut: `[ { "command": { "name": "ls", "id": "78587bb1-6ca2-4d2d-a223-1ee642514b97" }, "privileges": [ { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" } ] }, { "command": { "name": "cat", "id": "eb6fc295-a46c-4407-a7a3-1438436d6aee" }, "privileges": [ { "name": "ea-write", "id": "caedabee-ea7a-4421-a608-ec04106e61da" }, { "name": "ea-read", "id": "a7c379ea-6e34-4017-8e86-eb545d7856a3" } ] } ]`, } return testEndpoint(ctx, t, rulesEngine, test) }). Feature() f.Test(t, feat) }