Merge pull request 'Detect whether polls are promised to be anonymous or publicising voter identity with their cast vote' (#1104) from Oneric/akkoma:poll-anon into develop
All checks were successful
ci/woodpecker/push/docs Pipeline was successful
ci/woodpecker/push/publish/4 Pipeline was successful
ci/woodpecker/push/publish/1 Pipeline was successful
ci/woodpecker/push/publish/2 Pipeline was successful

Reviewed-on: #1104
This commit is contained in:
Oneric 2026-04-04 19:32:31 +00:00
commit 601c5b107d
15 changed files with 636 additions and 6 deletions

View file

@ -12,6 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon-compatible `avatar_description` and `header_description` parameters are added to account API responses and as input for `PATCH /api/v1/accounts/update_credentials`
- `pleroma.background_image_descripption` is added to account API responses
- `pleroma_background_image_descripption` is added as a new parameter to `PATCH /api/v1/accounts/update_credentials`
- `GET /api/v1/statuses/:id` contains the new `poll.akkoma.anonymous` parameter if `poll` is non-null.
It relays if and whether the source instance promised to keep votes anonymous or disclose votes with voter identity.
There are no plans to enable creating non-anonymous polls in Akkoma, but some implementations do.
### Fixed
- fix date-time format in `* /api/v1/markers` to strictly conform to Mastodons ISO 8061 subset

View file

@ -49,6 +49,15 @@ Has these additional fields under the `pleroma` object:
- `parent_visible`: If the parent of this post is visible to the user or not.
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
Furthermore, has the following additional attributes under the `poll.akkoma` object *(if `poll` is non-null)*:
- `anonymous`: relays whether the poll creator promised to process votes anonymously *(`true`)*,
publishes votes with voter identity to some parties or the public *(`false`)*
or did not indicate how votes are processed (`null`).
Note this assumes any remotes claim about its process are true and
even if this is truthfully set to `true`, while no regular users ought to have access to voter identities,
the server operator of the polls home instance may in principle still be able to
extract voter identites via the database or side channels.
The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes:
- `content_type`: The content type of the status source.

View file

@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator do
field(:closed, ObjectValidators.DateTime)
field(:voters, {:array, ObjectValidators.ObjectID}, default: [])
field(:nonAnonymous, :boolean)
embeds_many(:anyOf, QuestionOptionsValidator)
embeds_many(:oneOf, QuestionOptionsValidator)
end

View file

@ -105,7 +105,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"https://purl.archive.org/socialweb/webfinger",
%{
"@language" => "und",
"htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm"
"htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm",
"sm" => "http://smithereen.software/ns#",
"nonAnonymous" => "sm:nonAnonymous"
}
]
}

View file

@ -58,6 +58,17 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
}
},
description: "Possible answers for the poll."
},
akkoma: %Schema{
type: :object,
properties: %{
anonymous: %Schema{
type: :boolean,
nullable: true,
description:
"Whether the poll creator indicates votes are processed anonymously (true), votes with voter identity will be published to some parties or the public (false) or did not indicate anything about vote confidentiality (null)"
}
}
}
},
example: %{
@ -81,7 +92,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
votes_count: 4
}
],
emojis: []
emojis: [],
akkoma: %{
anonymous: true
}
}
})
end

View file

@ -152,7 +152,13 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> DateTime.to_iso8601()
key = if Params.truthy_param?(data.poll[:multiple]), do: "anyOf", else: "oneOf"
poll = %{"type" => "Question", key => option_notes, "closed" => end_time}
poll = %{
"type" => "Question",
key => option_notes,
"closed" => end_time,
"nonAnonymous" => false
}
{:ok, {poll, emoji}}
end

View file

@ -11,6 +11,12 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
{end_time, expired} = end_time_and_expired(object)
{options, votes_count} = options_and_votes_count(options)
anonymous =
case object.data["nonAnonymous"] do
nil -> nil
val -> !val
end
poll = %{
# Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine
@ -21,7 +27,10 @@ defmodule Pleroma.Web.MastodonAPI.PollView do
votes_count: votes_count,
voters_count: voters_count(multiple, object),
options: options,
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]),
akkoma: %{
anonymous: anonymous
}
}
if params[:for] do

View file

@ -0,0 +1,25 @@
defmodule Pleroma.Repo.Migrations.MarkOldPollsAsAnonymous do
use Ecto.Migration
def up() do
# objects does not have a local flag.
# Pleroma.Web.Endpoint available during migrations, meaning we can't reliably
# get the local base url to test against instead.
# Thus we mustjoin either with the users or activity table to determine localality.
# The existing objects_actor_type' index is a perfect fit for this query and joining with users.
"""
UPDATE objects AS o
SET data = jsonb_set(data, '{nonAnonymous}', to_jsonb(false), true)
FROM users AS u
WHERE
o.data->>'type' = 'Question' AND
o.data->>'actor' = u.ap_id AND
u.local
;
"""
|> Pleroma.Repo.query!([], timeout: :infinity)
end
# No need to revert
def down(), do: :ok
end

View file

@ -0,0 +1,112 @@
{
"type": "Question",
"id": "https://smithereen.example/posts/poll-anon",
"attributedTo": "https://smithereen.example/users/1",
"content": "<p>Example poll for which the identity and choices of voters will be kept secret</p>",
"name": "Какую русскую раскладку клавиатуры вы используете на macOS?",
"published": "2024-04-19T13:34:51Z",
"tag": [],
"url": "https://smithereen.example/posts/poll-anon",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://smithereen.example/users/1/followers"
],
"cc": [],
"replies": {
"type": "Collection",
"id": "https://smithereen.example/posts/poll-anon/replies",
"first": {
"type": "CollectionPage",
"items": [],
"partOf": "https://smithereen.example/posts/poll-anon/replies",
"next": "https://smithereen.example/posts/poll-anon/replies?page=1"
}
},
"sensitive": false,
"likes": {
"type": "Collection",
"id": "https://smithereen.example/posts/poll-anon/likes",
"first": {
"type": "CollectionPage",
"items": [],
"partOf": "https://smithereen.example/posts/poll-anon/likes",
"next": "https://smithereen.example/posts/poll-anon/likes?page=1"
}
},
"interactionPolicy": {
"canQuote": {
"automaticApproval": "https://www.w3.org/ns/activitystreams#Public"
}
},
"oneOf": [
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-anon#options/8352",
"name": "Просто \"русская\"",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8352/votes",
"totalItems": 11,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-anon#options/8353",
"name": "Русская — ПК",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8353/votes",
"totalItems": 6,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-anon#options/8354",
"name": "Другая",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8354/votes",
"totalItems": 1,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-anon#options/8355",
"name": "Не пользуюсь macOS и/или русским языком",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8355/votes",
"totalItems": 17,
"items": []
}
}
],
"votersCount": 35,
"nonAnonymous": false,
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"sensitive": "as:sensitive",
"gts": "https://gotosocial.org/ns#",
"interactionPolicy": {
"@id": "gts:interactionPolicy",
"@type": "@id"
},
"canQuote": {
"@id": "gts:canQuote",
"@type": "@id"
},
"automaticApproval": {
"@id": "gts:automaticApproval",
"@type": "@id"
},
"toot": "http://joinmastodon.org/ns#",
"sm": "http://smithereen.software/ns#",
"votersCount": "toot:votersCount",
"nonAnonymous": "sm:nonAnonymous"
}
]
}

View file

@ -0,0 +1,112 @@
{
"type": "Question",
"id": "https://smithereen.example/posts/poll-disclosed",
"attributedTo": "https://smithereen.example/users/1",
"content": "<p>Example poll for which all votes will be published with voter identity</p>",
"name": "Какую русскую раскладку клавиатуры вы используете на macOS?",
"published": "2024-04-19T13:34:51Z",
"tag": [],
"url": "https://smithereen.example/posts/poll-disclosed",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://smithereen.example/users/1/followers"
],
"cc": [],
"replies": {
"type": "Collection",
"id": "https://smithereen.example/posts/poll-disclosed/replies",
"first": {
"type": "CollectionPage",
"items": [],
"partOf": "https://smithereen.example/posts/poll-disclosed/replies",
"next": "https://smithereen.example/posts/poll-disclosed/replies?page=1"
}
},
"sensitive": false,
"likes": {
"type": "Collection",
"id": "https://smithereen.example/posts/poll-disclosed/likes",
"first": {
"type": "CollectionPage",
"items": [],
"partOf": "https://smithereen.example/posts/poll-disclosed/likes",
"next": "https://smithereen.example/posts/poll-disclosed/likes?page=1"
}
},
"interactionPolicy": {
"canQuote": {
"automaticApproval": "https://www.w3.org/ns/activitystreams#Public"
}
},
"oneOf": [
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-disclosed#options/8352",
"name": "Просто \"русская\"",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8352/votes",
"totalItems": 11,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-disclosed#options/8353",
"name": "Русская — ПК",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8353/votes",
"totalItems": 6,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-disclosed#options/8354",
"name": "Другая",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8354/votes",
"totalItems": 1,
"items": []
}
},
{
"type": "Note",
"id": "https://smithereen.example/posts/poll-disclosed#options/8355",
"name": "Не пользуюсь macOS и/или русским языком",
"replies": {
"type": "Collection",
"id": "https://smithereen.example/activitypub/objects/polls/2231/options/8355/votes",
"totalItems": 17,
"items": []
}
}
],
"votersCount": 35,
"nonAnonymous": true,
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"sensitive": "as:sensitive",
"gts": "https://gotosocial.org/ns#",
"interactionPolicy": {
"@id": "gts:interactionPolicy",
"@type": "@id"
},
"canQuote": {
"@id": "gts:canQuote",
"@type": "@id"
},
"automaticApproval": {
"@id": "gts:automaticApproval",
"@type": "@id"
},
"toot": "http://joinmastodon.org/ns#",
"sm": "http://smithereen.software/ns#",
"votersCount": "toot:votersCount",
"nonAnonymous": "sm:nonAnonymous"
}
]
}

250
test/fixtures/smithereen/user.json vendored Normal file
View file

@ -0,0 +1,250 @@
{
"type": "Person",
"id": "https://smithereen.example/users/1",
"attachment": [
{
"type": "PropertyValue",
"name": "Website",
"value": "<a href=\"https://grishka.me\" rel=\"me\">https://grishka.me</a>"
},
{
"type": "PropertyValue",
"name": "Matrix",
"value": "<a href=\"https://matrix.to/#/@grishk:matrix.org\" rel=\"me\">@grishk:matrix.org</a>"
},
{
"type": "PropertyValue",
"name": "Telegram",
"value": "<a href=\"https://t.me/grishka\" rel=\"me\">grishka</a>"
},
{
"type": "PropertyValue",
"name": "Twitter",
"value": "<a href=\"https://twitter.com/grishka11\" rel=\"me\">grishka11</a>"
},
{
"type": "PropertyValue",
"name": "Git",
"value": "<a href=\"https://github.com/grishka\" rel=\"me\">https://github.com/grishka</a>"
}
],
"name": "Григорий Клюшников",
"icon": {
"type": "Image",
"width": 573,
"height": 572,
"cropRegion": [
0.26422762870788574,
0.3766937553882599,
0.7113820910453796,
0.9728997349739075
],
"image": {
"type": "Image",
"url": "https://smithereen.example/i/-QttwTPRJpjwkRrB1MuI1zS--NGKf8r-K18LZAkqQ6g/q:93/bG9jYWw6Ly8vcy91cGxvYWRzLzAxLzAwLzAwLzloTWJvTzgyQzZnSV9GeHZVVVFUdUgzT19mNC0wVUczRmFfTS53ZWJw.jpg",
"mediaType": "image/jpeg",
"width": 1280,
"height": 960
},
"url": "https://smithereen.example/i/EWM-QfEuhSLizijJLta-0NWvN0KT2YJNfclg-Uvkxag/c:573:572:nowe:338:362/q:93/bG9jYWw6Ly8vcy91cGxvYWRzLzAxLzAwLzAwLzloTWJvTzgyQzZnSV9GeHZVVVFUdUgzT19mNC0wVUczRmFfTS53ZWJw.jpg",
"mediaType": "image/jpeg"
},
"summary": "<p>I mostly post in Russian here. My English-language account is <a href=\"https://mastodon.social/@grishka\">@grishka@mastodon.social</a>.</p>",
"url": "https://smithereen.example/grishka",
"preferredUsername": "grishka",
"inbox": "https://smithereen.example/users/1/inbox",
"outbox": "https://smithereen.example/users/1/outbox",
"followers": "https://smithereen.example/users/1/followers",
"following": "https://smithereen.example/users/1/following",
"endpoints": {
"sharedInbox": "https://smithereen.example/activitypub/sharedInbox",
"collectionSimpleQuery": "https://smithereen.example/users/1/collectionQuery"
},
"publicKey": {
"id": "https://smithereen.example/users/1#main-key",
"owner": "https://smithereen.example/users/1",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjlakm+i/d9ER/hIeR7Kf\niFW+SdLZj2SkKIeM8cmR+YFJuh9ghFqXrkFEjcaqUnAFqe5gYDNSQACnDLA8y4Dn\nzjfGNIohKAnRoa9x6GORmfKQvcnjaTZ53S1NvUiPPyc0Pv/vfCtY/Ab0CEXe5BLq\nL38oZn817Jf7pBrPRTYH7m012kvwAUTT6k0Y8lPITBEG7nzYbbuGcrN9Y/RDdwE0\n8jmBXlZ45bahRH3VNXVpQE17dCzJB+7k+iJ1R7YCoI+DuMlBYGXGE2KVk46NZTuL\nnOjFV9SyXfWX4/SrJM4oxev+SX2N75tQgmNZmVVHeqg2ZcbC0WCfNjJOi2HHS9Mu\njwIDAQAB\n-----END PUBLIC KEY-----\n"
},
"wall": "https://smithereen.example/users/1/wall",
"wallComments": "https://smithereen.example/users/1/wallComments",
"discoverable": true,
"indexable": true,
"status": {
"type": "ActorStatus",
"id": "https://smithereen.example/users/1/statuses/1747352717",
"attributedTo": "https://smithereen.example/users/1",
"content": "Гришка, верни стену!!!1",
"published": "2025-05-15T23:45:17.460Z"
},
"firstName": "Григорий",
"lastName": "Клюшников",
"middleName": "Александрович",
"vcard:bday": "1993-01-22",
"gender": "http://schema.org#Male",
"supportsFriendRequests": true,
"friends": "https://smithereen.example/users/1/friends",
"groups": "https://smithereen.example/users/1/groups",
"capabilities": {
"supportsFriendRequests": true
},
"privacySettings": {
"wallPosting": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"wallPostVisibility": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"commenting": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"groupInvitations": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"directMessages": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"photoTagging": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
},
"photoTagList": {
"allowedTo": [
"https://www.w3.org/ns/activitystreams#Public"
]
}
},
"activities": "Разрабатываю для федивёрса, приближаю уход централизованных соцсетей в небытие, возвращаю 2007й, бесконечно ругаюсь на ИТ-индустрию",
"interests": "Децентрализация, социальные сети, программирование, user experience, исследования о продлении жизни и лечении старения",
"favoriteTvShows": "Black Mirror, Stargate, Upload",
"favoriteQuotes": "\"Когда доделаем и оттестируем\"\n\"Всё невозможное возможно, знаю точно\"",
"politicalViews": "sm:Libertarian",
"personalPriority": "sm:ImprovingTheWorld",
"smokingViews": "sm:Negative",
"alcoholViews": "sm:Negative",
"vcard:Address": "St Petersburg",
"relationshipStatus": "sm:Complicated",
"photoAlbums": "https://smithereen.example/users/1/albums",
"manuallyApprovesFollowers": false,
"taggedPhotos": "https://smithereen.example/users/1/tagged",
"suspended": false,
"featured": "https://smithereen.example/users/1/pinnedPosts",
"apps": "https://smithereen.example/users/1/apps",
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"value": "http://schema.org#value",
"PropertyValue": "http://schema.org#PropertyValue",
"sm": "http://smithereen.software/ns#",
"cropRegion": {
"@id": "sm:cropRegion",
"@container": "@list"
},
"wall": {
"@id": "sm:wall",
"@type": "@id"
},
"wallComments": {
"@id": "sm:wallComments",
"@type": "@id"
},
"collectionSimpleQuery": "sm:collectionSimpleQuery",
"toot": "http://joinmastodon.org/ns#",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"status": {
"@id": "sm:status",
"@type": "@id"
},
"ActorStatus": "sm:ActorStatus",
"sc": "http://schema.org#",
"firstName": "sc:givenName",
"lastName": "sc:familyName",
"middleName": "sc:additionalName",
"gender": {
"@id": "sc:gender",
"@type": "sc:GenderType"
},
"maidenName": "sm:maidenName",
"friends": {
"@id": "sm:friends",
"@type": "@id"
},
"groups": {
"@id": "sm:groups",
"@type": "@id"
},
"vcard": "http://www.w3.org/2006/vcard/ns#",
"capabilities": "litepub:capabilities",
"supportsFriendRequests": "sm:supportsFriendRequests",
"litepub": "http://litepub.social/ns#",
"privacySettings": "sm:privacySettings",
"allowedTo": "sm:allowedTo",
"except": "sm:except",
"wallPosting": "sm:wallPosting",
"wallPostVisibility": "sm:wallPostVisibility",
"commenting": "sm:commenting",
"groupInvitations": "sm:groupInvitations",
"directMessages": "sm:directMessages",
"photoTagging": "sm:photoTagging",
"photoTagList": "sm:photoTagList",
"activities": "sm:activities",
"interests": "sm:interests",
"favoriteTvShows": "sm:favoriteTvShows",
"favoriteQuotes": "sm:favoriteQuotes",
"politicalViews": {
"@id": "sm:politicalViews",
"@type": "@id"
},
"personalPriority": {
"@id": "sm:personalPriority",
"@type": "@id"
},
"smokingViews": {
"@id": "sm:smokingViews",
"@type": "@id"
},
"alcoholViews": {
"@id": "sm:alcoholViews",
"@type": "@id"
},
"relationshipStatus": {
"@id": "sm:relationshipStatus",
"@type": "@id"
},
"relationshipPartner": {
"@id": "sm:relationshipPartner",
"@type": "@id"
},
"photoAlbums": {
"@id": "sm:photoAlbums",
"@type": "@id"
},
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"taggedPhotos": {
"@id": "sm:taggedPhotos",
"@type": "@id"
},
"suspended": "toot:suspended",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"apps": {
"@id": "sm:apps",
"@type": "@id"
}
},
"https://w3id.org/security/v1"
]
}

View file

@ -146,7 +146,9 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
"https://purl.archive.org/socialweb/webfinger",
%{
"@language" => "und",
"htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm"
"htmlMfm" => "https://w3id.org/fep/c16b#htmlMfm",
"nonAnonymous" => "sm:nonAnonymous",
"sm" => "http://smithereen.software/ns#"
}
]
}

View file

@ -36,6 +36,27 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
assert result["@context"]
end
test "renders a local poll with anonymitiy promise" do
poller = insert(:user, local: true)
{:ok, activity} =
CommonAPI.post(poller, %{
status: "nemui...",
poll: %{options: ["suya", "nini", "eep"], expires_in: 10}
})
result = ObjectView.render("object.json", %{object: activity})
assert result["id"] == activity.data["id"]
assert result["type"] == "Create"
resobj = result["object"]
assert is_map(resobj)
assert resobj["type"] == "Question"
assert resobj["nonAnonymous"] == false
end
describe "note activity's `replies` collection rendering" do
setup do: clear_config([:activitypub, :note_replies_output_limit], 5)

View file

@ -43,7 +43,11 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do
%{title: "why are you even asking?", votes_count: 0}
],
votes_count: 0,
voters_count: nil
voters_count: nil,
akkoma: %{
# locally created polls are always anonymous
anonymous: true
}
}
result = PollView.render("show.json", %{object: object})
@ -137,6 +141,27 @@ defmodule Pleroma.Web.MastodonAPI.PollViewTest do
assert result[:expired] == false
end
test "does not make false claims in absence of vote anonymity information" do
object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i", fetch: true)
result = PollView.render("show.json", %{object: object})
assert result[:akkoma][:anonymous] == nil
end
test "indicates promise of anonymity" do
object = Object.normalize("https://smithereen.example/posts/poll-anon", fetch: true)
result = PollView.render("show.json", %{object: object})
assert result[:akkoma][:anonymous] == true
end
test "indicates assured vote and identity disclosure" do
object = Object.normalize("https://smithereen.example/posts/poll-disclosed", fetch: true)
result = PollView.render("show.json", %{object: object})
assert result[:akkoma][:anonymous] == false
end
test "doesn't strips HTML tags" do
user = insert(:user)

View file

@ -1812,6 +1812,45 @@ defmodule HttpRequestMock do
}}
end
def get("https://smithereen.example/users/1" = url, _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
url: url,
body: File.read!("test/fixtures/smithereen/user.json"),
headers: [
{"content-type",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
]
}}
end
def get("https://smithereen.example/posts/poll-disclosed" = url, _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
url: url,
body: File.read!("test/fixtures/smithereen/poll_nonanonymous.json"),
headers: [
{"content-type",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
]
}}
end
def get("https://smithereen.example/posts/poll-anon" = url, _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
url: url,
body: File.read!("test/fixtures/smithereen/poll_anonymous.json"),
headers: [
{"content-type",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
]
}}
end
def get(url, query, body, headers) do
{:error,
"Mock response not implemented for GET #{inspect(url)}, #{query}, #{inspect(body)}, #{inspect(headers)}"}