paging.test.exs (6737B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2022 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule ZenflowsTest.DB.Paging do 19 use ZenflowsTest.Help.EctoCase, async: true 20 21 # What we are testing here is a bit interesting. Because, you see, 22 # what we actually care about is dependant on the number of records we 23 # ask for (referred to by "num" from now on). This is because of we 24 # always try to fetch num+1 records. This basically means that we'll 25 # have a table of possible cases: 26 # 27 # num | len(edges) 28 # ----+----------- 29 # 0 | 0 30 # 0 | 1 31 # ----+----------- 32 # 1 | 0 33 # 1 | 1 34 # 1 | 2 35 # ----+----------- 36 # 2 | 0 37 # 2 | 1 38 # 2 | 2 39 # 2 | 3 40 # ----+----------- 41 # 3 | 0 42 # 3 | 1 43 # 3 | 2 44 # 3 | 3 45 # 3 | 4 46 # ----+----------- 47 # 4 | 0 48 # 4 | 1 49 # 4 | 2 50 # 4 | 3 51 # 4 | 4 52 # 4 | 5 53 54 # Here, we cover the cases of: 55 # num | len(edges) 56 # ----+----------- 57 # 0 | 0 58 # 1 | 0 59 # 2 | 0 60 # 3 | 0 61 # 4 | 0 62 test "num>=0 && len(edges)==0:" do 63 Enum.each(0..10, fn n -> 64 assert %{data: %{"people" => data}} = 65 run!(""" 66 query ($n: Int!) { 67 people (first: $n) {...people} 68 } 69 """, vars: %{"n" => n}) 70 71 assert [] = Map.fetch!(data, "edges") 72 73 assert %{ 74 "startCursor" => nil, 75 "endCursor" => nil, 76 "hasPreviousPage" => false, 77 "hasNextPage" => false, 78 "totalCount" => 0, 79 "pageLimit" => ^n, 80 } = Map.fetch!(data, "pageInfo") 81 end) 82 end 83 84 # Here, we cover the cases of: 85 # num | len(edges) 86 # ----+----------- 87 # 1 | 1 88 # 2 | 2 89 # 3 | 3 90 # 4 | 4 91 test "num>=1 && len(edges)==num:" do 92 Enum.reduce(1..10, [], fn n, pers -> 93 last = %{id: last_cur} = Factory.insert!(:person) 94 pers = pers ++ [last] 95 [%{id: first_cur} | _] = pers 96 97 assert %{data: %{"people" => data}} = 98 run!(""" 99 query ($n: Int!) { 100 people (first: $n) {...people} 101 } 102 """, vars: %{"n" => n}) 103 104 edges = Map.fetch!(data, "edges") 105 assert length(edges) == n 106 107 assert %{ 108 "startCursor" => ^first_cur, 109 "endCursor" => ^last_cur, 110 "hasPreviousPage" => false, 111 "hasNextPage" => false, 112 "totalCount" => ^n, 113 "pageLimit" => ^n, 114 } = Map.fetch!(data, "pageInfo") 115 116 pers 117 end) 118 end 119 120 # Here, we cover the cases of: 121 # num | len(edges) 122 # ----+----------- 123 # 0 | 1 124 # 1 | 2 125 # 2 | 3 126 # 3 | 4 127 # 4 | 5 128 test "num>=0 && len(edges)==num+1:" do 129 Enum.reduce(0..10, [], fn n, pers -> 130 pers = pers ++ [Factory.insert!(:person)] 131 {tmp, _} = Enum.split(pers, n) 132 first = List.first(tmp) 133 last = List.last(tmp) 134 first_cur = if first != nil, do: first.id, else: nil 135 last_cur = if last != nil, do: last.id, else: nil 136 137 assert %{data: %{"people" => data}} = 138 run!(""" 139 query ($n: Int!) { 140 people (first: $n) {...people} 141 } 142 """, vars: %{"n" => n}) 143 144 edges = Map.fetch!(data, "edges") 145 assert length(edges) == n 146 147 assert %{ 148 "startCursor" => ^first_cur, 149 "endCursor" => ^last_cur, 150 "hasPreviousPage" => false, 151 "hasNextPage" => true, 152 "totalCount" => ^n, 153 "pageLimit" => ^n, 154 } = Map.fetch!(data, "pageInfo") 155 156 pers 157 end) 158 end 159 160 # Here, we cover the last case, which prooves we cover all the cases 161 # (this is so because of the fact that we only deal with len(edges)<num 162 # cases, where num>=1): 163 # num | len(edges) 164 # ----+----------- 165 # 2 | 1 166 # ----+----------- 167 # 3 | 1 168 # 3 | 2 169 # ----+----------- 170 # 4 | 1 171 # 4 | 2 172 # 4 | 3 173 test "num>=2 && len(edges)>=0 && len(edges)<num:" do 174 Enum.reduce(1..9, [], fn e, pers -> 175 pers = pers ++ [Factory.insert!(:person)] 176 177 Enum.each(2..10, fn n -> 178 if e < n do 179 assert %{data: %{"people" => data}} = 180 run!(""" 181 query ($n: Int!) { 182 people (first: $n) {...people} 183 } 184 """, vars: %{"n" => n}) 185 186 edges = Map.fetch!(data, "edges") 187 assert length(edges) == e 188 189 %{id: first_cur} = List.first(pers) 190 %{id: last_cur} = List.last(pers) 191 192 assert %{ 193 "startCursor" => ^first_cur, 194 "endCursor" => ^last_cur, 195 "hasPreviousPage" => false, 196 "hasNextPage" => false, 197 "totalCount" => ^e, 198 "pageLimit" => ^n, 199 } = Map.fetch!(data, "pageInfo") 200 end 201 end) 202 203 pers 204 end) 205 end 206 207 # We're dealing with cursors here now. Most of the cases are the 208 # same as the ones without the cursors, so we omit them. 209 210 # Here, we cover the cases of: 211 # num | len(edges) 212 # ----+----------- 213 # 1 | 1 214 # 2 | 2 215 # 3 | 3 216 # 4 | 4 217 test "with cursor: num>=1 && len(edges)==num:" do 218 Enum.each(1..10, fn n -> 219 p = Factory.insert!(:person) 220 221 assert %{data: %{"people" => data}} = 222 run!(""" 223 query ($cur: ID! $n: Int!) { 224 people (after: $cur first: $n) {...people} 225 } 226 """, vars: %{"n" => n, "cur" => p.id}) 227 228 assert [] = Map.fetch!(data, "edges") 229 230 assert %{ 231 "startCursor" => nil, 232 "endCursor" => nil, 233 "hasPreviousPage" => true, # spec says so if we can't determine 234 "hasNextPage" => false, 235 "totalCount" => 0, 236 "pageLimit" => ^n, 237 } = Map.fetch!(data, "pageInfo") 238 end) 239 end 240 241 # Here, we cover the cases of: 242 # num | len(edges) 243 # ----+----------- 244 # 1 | 2 245 # 2 | 3 246 # 3 | 4 247 # 4 | 5 248 test "with cursor: num>=1 && len(edges)==num+1:" do 249 pers = [Factory.insert!(:person)] 250 Enum.reduce(1..10, pers, fn n, pers -> 251 %{id: after_cur} = List.last(pers) 252 last = %{id: last_cur} = Factory.insert!(:person) 253 pers = pers ++ [last] 254 255 assert %{data: %{"people" => data}} = 256 run!(""" 257 query ($cur: ID! $n: Int!) { 258 people (after: $cur first: $n) {...people} 259 } 260 """, vars: %{"n" => n, "cur" => after_cur}) 261 262 assert [_] = Map.fetch!(data, "edges") 263 264 assert %{ 265 "startCursor" => ^last_cur, 266 "endCursor" => ^last_cur, 267 "hasPreviousPage" => true, # spec 268 "hasNextPage" => false, 269 "totalCount" => 1, 270 "pageLimit" => ^n, 271 } = Map.fetch!(data, "pageInfo") 272 273 pers 274 end) 275 end 276 277 @spec run!(String.t(), Keyword.t()) :: Absinthe.run_result() 278 def run!(doc, opts \\ []) do 279 """ 280 #{doc} 281 fragment people on PersonConnection { 282 pageInfo { 283 startCursor 284 endCursor 285 hasPreviousPage 286 hasNextPage 287 totalCount 288 pageLimit 289 } 290 edges { 291 cursor 292 node {id} 293 } 294 } 295 """ 296 |> ZenflowsTest.Help.AbsinCase.run!(opts) 297 end 298 end