Test user delete while that user has an active changes feed (see issue 809)
(t *testing.T)
| 917 | |
| 918 | // Test user delete while that user has an active changes feed (see issue 809) |
| 919 | func TestUserDeleteDuringChangesWithAccess(t *testing.T) { |
| 920 | |
| 921 | base.SetUpTestLogging(t, base.LevelInfo, base.KeyChanges, base.KeyCache, base.KeyHTTP) |
| 922 | |
| 923 | rtConfig := RestTesterConfig{SyncFn: `function(doc) {channel(doc.channel); if(doc.type == "setaccess") { access(doc.owner, doc.channel);}}`} |
| 924 | rt := NewRestTester(t, &rtConfig) |
| 925 | defer rt.Close() |
| 926 | |
| 927 | response := rt.SendAdminRequest("PUT", "/db/_user/bernard", `{"name":"bernard", "password":"letmein", "admin_channels":["foo"]}`) |
| 928 | RequireStatus(t, response, 201) |
| 929 | |
| 930 | var wg sync.WaitGroup |
| 931 | wg.Add(1) |
| 932 | go func() { |
| 933 | defer wg.Done() |
| 934 | changesResponse := rt.Send(RequestByUser("GET", "/{{.keyspace}}/_changes?feed=continuous&since=0&timeout=3000", "", "bernard")) |
| 935 | // When testing single threaded, this reproduces the issue described in #809. |
| 936 | // When testing multithreaded (-cpu 4 -race), there are three (valid) possibilities" |
| 937 | // 1. The DELETE gets processed before the _changes auth completes: this will return 401 |
| 938 | // 2. The _changes request gets processed before the DELETE: the changes response will be closed when the user is deleted |
| 939 | // 3. The DELETE is processed after the _changes auth completes, but before the MultiChangesFeed is instantiated. The |
| 940 | // changes feed doesn't have a trigger to attempt a reload of the user in this scenario, so will continue until disconnected |
| 941 | // by the client. This should be fixed more generally (to terminate all active user sessions when the user is deleted, not just |
| 942 | // changes feeds) but that enhancement is too high risk to introduce at this time. The timeout on changes will terminate the unit |
| 943 | // test. |
| 944 | if changesResponse.Code == 401 { |
| 945 | // case 1 - ok |
| 946 | } else { |
| 947 | // case 2 - ensure no error processing the changes response. The number of entries may vary, depending |
| 948 | // on whether the changes loop performed an additional iteration before catching the deleted user. |
| 949 | _, err := rt.ReadContinuousChanges(changesResponse) |
| 950 | assert.NoError(t, err) |
| 951 | } |
| 952 | }() |
| 953 | |
| 954 | // TODO: sleep required to ensure the changes feed iteration starts before the delete gets processed. |
| 955 | time.Sleep(500 * time.Millisecond) |
| 956 | rt.SendAdminRequest("PUT", "/{{.keyspace}}/bernard_doc1", `{"type":"setaccess", "owner":"bernard","channel":"foo"}`) |
| 957 | rt.SendAdminRequest("DELETE", "/db/_user/bernard", "") |
| 958 | rt.SendAdminRequest("PUT", "/{{.keyspace}}/manny_doc1", `{"type":"setaccess", "owner":"manny","channel":"bar"}`) |
| 959 | rt.SendAdminRequest("PUT", "/{{.keyspace}}/bernard_doc2", `{"type":"general", "channel":"foo"}`) |
| 960 | |
| 961 | // case 3 |
| 962 | for i := 0; i <= 5; i++ { |
| 963 | docId := fmt.Sprintf("/{{.keyspace}}/bernard_doc%d", i+3) |
| 964 | response = rt.SendAdminRequest("PUT", docId, `{"type":"setaccess", "owner":"bernard", "channel":"foo"}`) |
| 965 | RequireStatus(t, response, http.StatusCreated) |
| 966 | } |
| 967 | |
| 968 | wg.Wait() |
| 969 | } |
| 970 | |
| 971 | // Helper functions |
| 972 |
nothing calls this directly
no test coverage detected