Auto-indexing service and GraphQL API for AT Protocol Records
quickslice.slices.network/
atproto
gleam
graphql
1import gleam/option.{None, Some}
2import gleeunit/should
3import lib/oauth/scopes/parser
4import lib/oauth/scopes/types
5
6pub fn parse_atproto_test() {
7 parser.parse_scope("atproto")
8 |> should.be_ok
9 |> should.equal(types.Static(types.Atproto))
10}
11
12pub fn parse_transition_generic_test() {
13 parser.parse_scope("transition:generic")
14 |> should.be_ok
15 |> should.equal(types.Static(types.TransitionGeneric))
16}
17
18pub fn parse_transition_email_test() {
19 parser.parse_scope("transition:email")
20 |> should.be_ok
21 |> should.equal(types.Static(types.TransitionEmail))
22}
23
24pub fn parse_transition_chat_bsky_test() {
25 parser.parse_scope("transition:chat.bsky")
26 |> should.be_ok
27 |> should.equal(types.Static(types.TransitionChatBsky))
28}
29
30pub fn parse_account_email_test() {
31 parser.parse_scope("account:email")
32 |> should.be_ok
33 |> should.equal(
34 types.Account(types.AccountScope(
35 attribute: types.EmailAttr,
36 action: types.Read,
37 )),
38 )
39}
40
41pub fn parse_account_email_with_action_test() {
42 parser.parse_scope("account:email?action=manage")
43 |> should.be_ok
44 |> should.equal(
45 types.Account(types.AccountScope(
46 attribute: types.EmailAttr,
47 action: types.Manage,
48 )),
49 )
50}
51
52pub fn parse_account_repo_test() {
53 parser.parse_scope("account:repo")
54 |> should.be_ok
55 |> should.equal(
56 types.Account(types.AccountScope(
57 attribute: types.RepoAttr,
58 action: types.Read,
59 )),
60 )
61}
62
63pub fn parse_account_status_test() {
64 parser.parse_scope("account:status?action=manage")
65 |> should.be_ok
66 |> should.equal(
67 types.Account(types.AccountScope(
68 attribute: types.StatusAttr,
69 action: types.Manage,
70 )),
71 )
72}
73
74pub fn parse_identity_handle_test() {
75 parser.parse_scope("identity:handle")
76 |> should.be_ok
77 |> should.equal(types.Identity(types.IdentityScope(attribute: types.Handle)))
78}
79
80pub fn parse_identity_all_test() {
81 parser.parse_scope("identity:*")
82 |> should.be_ok
83 |> should.equal(types.Identity(types.IdentityScope(attribute: types.All)))
84}
85
86pub fn parse_repo_wildcard_test() {
87 parser.parse_scope("repo:*")
88 |> should.be_ok
89 |> should.equal(
90 types.Repo(
91 types.RepoScope(collection: "*", actions: [
92 types.Create,
93 types.Update,
94 types.Delete,
95 ]),
96 ),
97 )
98}
99
100pub fn parse_repo_specific_collection_test() {
101 parser.parse_scope("repo:app.bsky.feed.post")
102 |> should.be_ok
103 |> should.equal(
104 types.Repo(
105 types.RepoScope(collection: "app.bsky.feed.post", actions: [
106 types.Create,
107 types.Update,
108 types.Delete,
109 ]),
110 ),
111 )
112}
113
114pub fn parse_repo_with_single_action_test() {
115 parser.parse_scope("repo:app.bsky.feed.post?action=create")
116 |> should.be_ok
117 |> should.equal(
118 types.Repo(
119 types.RepoScope(collection: "app.bsky.feed.post", actions: [types.Create]),
120 ),
121 )
122}
123
124pub fn parse_repo_with_multiple_actions_test() {
125 parser.parse_scope("repo:*?action=create&action=delete")
126 |> should.be_ok
127 |> should.equal(
128 types.Repo(
129 types.RepoScope(collection: "*", actions: [types.Create, types.Delete]),
130 ),
131 )
132}
133
134pub fn parse_blob_wildcard_test() {
135 parser.parse_scope("blob:*/*")
136 |> should.be_ok
137 |> should.equal(types.Blob(types.BlobScope(mime_type: "*/*")))
138}
139
140pub fn parse_blob_image_wildcard_test() {
141 parser.parse_scope("blob:image/*")
142 |> should.be_ok
143 |> should.equal(types.Blob(types.BlobScope(mime_type: "image/*")))
144}
145
146pub fn parse_blob_specific_type_test() {
147 parser.parse_scope("blob:image/png")
148 |> should.be_ok
149 |> should.equal(types.Blob(types.BlobScope(mime_type: "image/png")))
150}
151
152pub fn parse_blob_invalid_mime_test() {
153 parser.parse_scope("blob:invalid")
154 |> should.be_error
155}
156
157pub fn parse_rpc_specific_method_test() {
158 parser.parse_scope("rpc:app.bsky.feed.getFeed?aud=did:web:bsky.app")
159 |> should.be_ok
160 |> should.equal(
161 types.Rpc(types.RpcScope(
162 methods: ["app.bsky.feed.getFeed"],
163 audience: "did:web:bsky.app",
164 )),
165 )
166}
167
168pub fn parse_rpc_wildcard_method_specific_aud_test() {
169 parser.parse_scope("rpc:*?aud=did:web:api.bsky.app")
170 |> should.be_ok
171 |> should.equal(
172 types.Rpc(types.RpcScope(methods: ["*"], audience: "did:web:api.bsky.app")),
173 )
174}
175
176pub fn parse_rpc_missing_aud_test() {
177 parser.parse_scope("rpc:app.bsky.feed.getFeed")
178 |> should.be_error
179}
180
181pub fn parse_rpc_wildcard_both_test() {
182 // rpc:* with aud=* is invalid
183 parser.parse_scope("rpc:*?aud=*")
184 |> should.be_error
185}
186
187pub fn parse_include_simple_test() {
188 parser.parse_scope("include:app.bsky.feed")
189 |> should.be_ok
190 |> should.equal(
191 types.Include(types.IncludeScope(nsid: "app.bsky.feed", audience: None)),
192 )
193}
194
195pub fn parse_include_with_aud_test() {
196 parser.parse_scope("include:chat.bsky.moderation?aud=did:web:bsky.chat")
197 |> should.be_ok
198 |> should.equal(
199 types.Include(types.IncludeScope(
200 nsid: "chat.bsky.moderation",
201 audience: Some("did:web:bsky.chat"),
202 )),
203 )
204}
205
206pub fn parse_scopes_multiple_test() {
207 parser.parse_scopes("atproto repo:* account:email")
208 |> should.be_ok
209 |> should.equal([
210 types.Static(types.Atproto),
211 types.Repo(
212 types.RepoScope(collection: "*", actions: [
213 types.Create,
214 types.Update,
215 types.Delete,
216 ]),
217 ),
218 types.Account(types.AccountScope(
219 attribute: types.EmailAttr,
220 action: types.Read,
221 )),
222 ])
223}
224
225pub fn parse_scopes_empty_test() {
226 parser.parse_scopes("")
227 |> should.be_ok
228 |> should.equal([])
229}
230
231pub fn parse_scopes_single_invalid_fails_test() {
232 parser.parse_scopes("atproto invalid::: repo:*")
233 |> should.be_error
234}