Update the user data export archive format.
This commit introduces changes to the user data export archive format. This extends data set which is included in the archive. This data can be then imported to other pods when this feature is implemented. Also the commit adds the archive format json schema. ATM it is used in automatic tests only, but in future it will also be used to validate incoming archives.
This commit is contained in:
parent
1b1db3bb0c
commit
7374661e2f
41 changed files with 1469 additions and 171 deletions
3
Gemfile
3
Gemfile
|
|
@ -13,6 +13,7 @@ gem "unicorn-worker-killer", "0.4.4"
|
|||
|
||||
# Federation
|
||||
|
||||
gem "diaspora_federation-json_schema", "0.2.1"
|
||||
gem "diaspora_federation-rails", "0.2.1"
|
||||
|
||||
# API and JSON
|
||||
|
|
@ -277,6 +278,8 @@ group :test do
|
|||
|
||||
gem "fixture_builder", "0.5.0"
|
||||
gem "fuubar", "2.2.0"
|
||||
gem "json-schema-rspec", "0.0.4"
|
||||
gem "rspec-json_expectations", "~> 2.1"
|
||||
gem "test_after_commit", "1.1.0"
|
||||
|
||||
# Cucumber (integration tests)
|
||||
|
|
|
|||
|
|
@ -168,6 +168,7 @@ GEM
|
|||
nokogiri (~> 1.6, >= 1.6.8)
|
||||
typhoeus (~> 1.0)
|
||||
valid (~> 1.0)
|
||||
diaspora_federation-json_schema (0.2.1)
|
||||
diaspora_federation-rails (0.2.1)
|
||||
actionpack (>= 4.2, < 6)
|
||||
diaspora_federation (= 0.2.1)
|
||||
|
|
@ -334,6 +335,9 @@ GEM
|
|||
url_safe_base64
|
||||
json-schema (2.8.0)
|
||||
addressable (>= 2.4)
|
||||
json-schema-rspec (0.0.4)
|
||||
json-schema (~> 2.5)
|
||||
rspec
|
||||
jsonpath (0.8.5)
|
||||
multi_json
|
||||
jwt (1.5.6)
|
||||
|
|
@ -582,6 +586,7 @@ GEM
|
|||
rspec-expectations (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-json_expectations (2.1.0)
|
||||
rspec-mocks (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
|
|
@ -772,6 +777,7 @@ DEPENDENCIES
|
|||
devise (= 4.3.0)
|
||||
devise_lastseenable (= 0.0.6)
|
||||
diaspora-prosody-config (= 0.0.7)
|
||||
diaspora_federation-json_schema (= 0.2.1)
|
||||
diaspora_federation-rails (= 0.2.1)
|
||||
diaspora_federation-test (= 0.2.1)
|
||||
entypo-rails (= 3.0.0)
|
||||
|
|
@ -800,6 +806,7 @@ DEPENDENCIES
|
|||
js_image_paths (= 0.1.0)
|
||||
json (= 2.1.0)
|
||||
json-schema (= 2.8.0)
|
||||
json-schema-rspec (= 0.0.4)
|
||||
leaflet-rails (= 1.1.0)
|
||||
logging-rails (= 0.6.0)
|
||||
markerb (= 1.1.0)
|
||||
|
|
@ -858,6 +865,7 @@ DEPENDENCIES
|
|||
rb-inotify (= 0.9.10)
|
||||
redcarpet (= 3.4.0)
|
||||
responders (= 2.4.0)
|
||||
rspec-json_expectations (~> 2.1)
|
||||
rspec-rails (= 3.6.0)
|
||||
rubocop (= 0.49.1)
|
||||
ruby-oembed (= 0.12.0)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ class Person < ActiveRecord::Base
|
|||
has_many :posts, :foreign_key => :author_id, :dependent => :destroy # This person's own posts
|
||||
has_many :photos, :foreign_key => :author_id, :dependent => :destroy # This person's own photos
|
||||
has_many :comments, :foreign_key => :author_id, :dependent => :destroy # This person's own comments
|
||||
has_many :likes, foreign_key: :author_id, dependent: :destroy # This person's own likes
|
||||
has_many :participations, :foreign_key => :author_id, :dependent => :destroy
|
||||
has_many :poll_participations, foreign_key: :author_id, dependent: :destroy
|
||||
has_many :conversation_visibilities
|
||||
|
||||
has_many :roles
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
module Export
|
||||
class CommentSerializer < ActiveModel::Serializer
|
||||
attributes :guid,
|
||||
:text,
|
||||
:post_guid
|
||||
|
||||
def post_guid
|
||||
object.post.guid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -2,11 +2,33 @@ module Export
|
|||
class ContactSerializer < ActiveModel::Serializer
|
||||
attributes :sharing,
|
||||
:receiving,
|
||||
:following,
|
||||
:followed,
|
||||
:person_guid,
|
||||
:person_name,
|
||||
:person_first_name,
|
||||
:person_diaspora_handle
|
||||
:account_id,
|
||||
:public_key
|
||||
|
||||
has_many :aspects, each_serializer: Export::AspectSerializer
|
||||
has_many :contact_groups_membership
|
||||
|
||||
def following
|
||||
object.sharing
|
||||
end
|
||||
|
||||
def followed
|
||||
object.receiving
|
||||
end
|
||||
|
||||
def account_id
|
||||
object.person_diaspora_handle
|
||||
end
|
||||
|
||||
def contact_groups_membership
|
||||
object.aspects.map(&:name)
|
||||
end
|
||||
|
||||
def public_key
|
||||
object.person.serialized_public_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
34
app/serializers/export/others_data_serializer.rb
Normal file
34
app/serializers/export/others_data_serializer.rb
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
module Export
|
||||
class OthersDataSerializer < ActiveModel::Serializer
|
||||
# Relayables of other people in the archive: comments, likes, participations, poll participations where author is
|
||||
# the archive owner
|
||||
has_many :relayables, each_serializer: FederationEntitySerializer
|
||||
|
||||
# Parent posts of user's own relayables. We have to save metadata to use
|
||||
# it in case when posts temporary unavailable on the target pod.
|
||||
has_many :posts, each_serializer: FederationEntitySerializer
|
||||
|
||||
# Authors of posts where we participated and authors are not in contacts
|
||||
has_many :non_contact_authors, each_serializer: PersonMetadataSerializer
|
||||
|
||||
private
|
||||
|
||||
def relayables
|
||||
%i[comments likes poll_participations].map {|relayable|
|
||||
others_relayables.send(relayable)
|
||||
}.sum
|
||||
end
|
||||
|
||||
def others_relayables
|
||||
@others_relayables ||= Diaspora::Exporter::OthersRelayables.new(object.person_id)
|
||||
end
|
||||
|
||||
def posts
|
||||
@posts ||= Diaspora::Exporter::PostsWithActivity.new(object).query
|
||||
end
|
||||
|
||||
def non_contact_authors
|
||||
Diaspora::Exporter::NonContactAuthors.new(posts, object).query
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/serializers/export/own_post_serializer.rb
Normal file
33
app/serializers/export/own_post_serializer.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
module Export
|
||||
# This is a serializer for the user's own posts
|
||||
class OwnPostSerializer < FederationEntitySerializer
|
||||
# Only for public posts.
|
||||
# Includes URIs of pods which must be notified on the post updates.
|
||||
# Must always include local pod URI since we will want all the updates on the post if user migrates.
|
||||
has_many :subscribed_pods_uris
|
||||
|
||||
# Only for private posts.
|
||||
# Includes diaspora* IDs of people who must be notified on post updates.
|
||||
has_many :subscribed_users_ids
|
||||
|
||||
# Normally accepts Post as an object.
|
||||
def initialize(*)
|
||||
super
|
||||
self.except = [excluded_subscription_key]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def subscribed_pods_uris
|
||||
object.subscribed_pods_uris.push(AppConfig.pod_uri.to_s)
|
||||
end
|
||||
|
||||
def subscribed_users_ids
|
||||
object.subscribers.map(&:diaspora_handle)
|
||||
end
|
||||
|
||||
def excluded_subscription_key
|
||||
entity.public ? :subscribed_users_ids : :subscribed_pods_uris
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/serializers/export/own_relayables_serializer.rb
Normal file
13
app/serializers/export/own_relayables_serializer.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module Export
|
||||
# This is a serializer for the user's own relayables. We remove signature from the own relayables since it isn't
|
||||
# useful and takes space.
|
||||
class OwnRelayablesSerializer < FederationEntitySerializer
|
||||
private
|
||||
|
||||
def modify_serializable_object(hash)
|
||||
super.tap {|hash|
|
||||
hash[:entity_data].delete(:author_signature)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/serializers/export/person_metadata_serializer.rb
Normal file
17
app/serializers/export/person_metadata_serializer.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module Export
|
||||
class PersonMetadataSerializer < ActiveModel::Serializer
|
||||
attributes :guid,
|
||||
:account_id,
|
||||
:public_key
|
||||
|
||||
private
|
||||
|
||||
def account_id
|
||||
object.diaspora_handle
|
||||
end
|
||||
|
||||
def public_key
|
||||
object.serialized_public_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
module Export
|
||||
class PostSerializer < ActiveModel::Serializer
|
||||
attributes :guid,
|
||||
:text,
|
||||
:public,
|
||||
:diaspora_handle,
|
||||
:type,
|
||||
:likes_count,
|
||||
:comments_count,
|
||||
:reshares_count,
|
||||
:created_at
|
||||
end
|
||||
end
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
module Export
|
||||
class ProfileSerializer < ActiveModel::Serializer
|
||||
attributes :first_name,
|
||||
:last_name,
|
||||
:gender,
|
||||
:bio,
|
||||
:birthday,
|
||||
:location,
|
||||
:image_url,
|
||||
:diaspora_handle,
|
||||
:searchable,
|
||||
:nsfw
|
||||
end
|
||||
end
|
||||
|
|
@ -1,24 +1,49 @@
|
|||
module Export
|
||||
class UserSerializer < ActiveModel::Serializer
|
||||
attributes :name,
|
||||
attributes :username,
|
||||
:email,
|
||||
:language,
|
||||
:username,
|
||||
:serialized_private_key,
|
||||
:private_key,
|
||||
:disable_mail,
|
||||
:show_community_spotlight_in_stream,
|
||||
:auto_follow_back,
|
||||
:auto_follow_back_aspect,
|
||||
:strip_exif
|
||||
has_one :profile, serializer: Export::ProfileSerializer
|
||||
has_many :aspects, each_serializer: Export::AspectSerializer
|
||||
has_one :profile, serializer: FederationEntitySerializer
|
||||
has_many :contact_groups, each_serializer: Export::AspectSerializer
|
||||
has_many :contacts, each_serializer: Export::ContactSerializer
|
||||
has_many :posts, each_serializer: Export::PostSerializer
|
||||
has_many :comments, each_serializer: Export::CommentSerializer
|
||||
has_many :posts, each_serializer: Export::OwnPostSerializer
|
||||
has_many :followed_tags
|
||||
has_many :post_subscriptions
|
||||
|
||||
def comments
|
||||
object.person.comments
|
||||
has_many :relayables, each_serializer: Export::OwnRelayablesSerializer
|
||||
|
||||
private
|
||||
|
||||
def relayables
|
||||
[*comments, *likes, *poll_participations]
|
||||
end
|
||||
|
||||
%i[comments likes poll_participations].each {|collection|
|
||||
delegate collection, to: :person
|
||||
}
|
||||
|
||||
delegate :person, to: :object
|
||||
|
||||
def contact_groups
|
||||
object.aspects
|
||||
end
|
||||
|
||||
def private_key
|
||||
object.serialized_private_key
|
||||
end
|
||||
|
||||
def followed_tags
|
||||
object.followed_tags.map(&:name)
|
||||
end
|
||||
|
||||
def post_subscriptions
|
||||
Post.subscribed_by(object).pluck(:guid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
16
app/serializers/federation_entity_serializer.rb
Normal file
16
app/serializers/federation_entity_serializer.rb
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# This is an ActiveModel::Serializer based class which uses DiasporaFederation::Entity JSON serialization
|
||||
# features in order to serialize local DB objects. To determine a type of entity class to use the same routines
|
||||
# are used as for federation messages generation.
|
||||
class FederationEntitySerializer < ActiveModel::Serializer
|
||||
include SerializerPostProcessing
|
||||
|
||||
private
|
||||
|
||||
def modify_serializable_object(hash)
|
||||
hash.merge(entity.to_json)
|
||||
end
|
||||
|
||||
def entity
|
||||
@entity ||= Diaspora::Federation::Entities.build(object)
|
||||
end
|
||||
end
|
||||
20
app/serializers/serializer_post_processing.rb
Normal file
20
app/serializers/serializer_post_processing.rb
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# This module encapsulates knowledge about the way AMS works with the serializable object.
|
||||
# The main responsibility of this module is to allow changing resulting object just before the
|
||||
# JSON serialization happens.
|
||||
module SerializerPostProcessing
|
||||
# serializable_object output is used in AMS to produce a hash from input object that is passed to JSON serializer.
|
||||
# serializable_object of ActiveModel::Serializer is not documented as officialy available API
|
||||
# NOTE: if we ever move to AMS 0.10, this method was renamed there to serializable_hash
|
||||
def serializable_object(options={})
|
||||
modify_serializable_object(super)
|
||||
end
|
||||
|
||||
# Users of this module may override this method in order to change serializable_object after
|
||||
# the serializable hash generation and before its serialization.
|
||||
def modify_serializable_object(hash)
|
||||
hash
|
||||
end
|
||||
|
||||
# except is an array of keys that are excluded from serialized_object before JSON serialization
|
||||
attr_accessor :except
|
||||
end
|
||||
|
|
@ -101,7 +101,8 @@ class AccountDeleter
|
|||
end
|
||||
|
||||
def ignored_or_special_ar_person_associations
|
||||
%i(comments contacts notification_actors notifications owner profile conversation_visibilities pod)
|
||||
%i[comments likes poll_participations contacts notification_actors notifications owner profile
|
||||
conversation_visibilities pod]
|
||||
end
|
||||
|
||||
def mark_account_deletion_complete
|
||||
|
|
|
|||
|
|
@ -6,22 +6,23 @@ module Diaspora
|
|||
|
||||
class Exporter
|
||||
|
||||
SERIALIZED_VERSION = '1.0'
|
||||
SERIALIZED_VERSION = "2.0".freeze
|
||||
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
def execute
|
||||
@export ||= JSON.generate serialized_user.merge(version: SERIALIZED_VERSION)
|
||||
JSON.generate full_archive
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def serialized_user
|
||||
@serialized_user ||= Export::UserSerializer.new(@user).as_json
|
||||
def full_archive
|
||||
{version: SERIALIZED_VERSION}
|
||||
.merge(Export::UserSerializer.new(@user).as_json)
|
||||
.merge(Export::OthersDataSerializer.new(@user).as_json)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@ module Diaspora
|
|||
# This class allows to query posts where a person made any activity (submitted comments,
|
||||
# likes, participations or poll participations).
|
||||
class PostsWithActivity
|
||||
# TODO: docs
|
||||
# @param user [User] user who the activity belongs to (the one who liked, commented posts, etc)
|
||||
def initialize(user)
|
||||
@user = user
|
||||
end
|
||||
|
||||
# TODO: docs
|
||||
# Create a request of posts with activity
|
||||
# @return [Post::ActiveRecord_Relation]
|
||||
def query
|
||||
Post.from("(#{sql_union_all_activities}) AS posts")
|
||||
end
|
||||
|
|
@ -26,7 +27,7 @@ module Diaspora
|
|||
end
|
||||
|
||||
def all_activities
|
||||
[comments_activity, likes_activity, subscriptions, polls_activity].compact
|
||||
[comments_activity, likes_activity, subscriptions, polls_activity, reshares_activity]
|
||||
end
|
||||
|
||||
def likes_activity
|
||||
|
|
@ -41,6 +42,10 @@ module Diaspora
|
|||
other_people_posts.subscribed_by(user)
|
||||
end
|
||||
|
||||
def reshares_activity
|
||||
other_people_posts.reshared_by(person)
|
||||
end
|
||||
|
||||
def polls_activity
|
||||
StatusMessage.where.not(author_id: person.id).joins(:poll_participations)
|
||||
.where(poll_participations: {author_id: person.id})
|
||||
|
|
|
|||
|
|
@ -50,6 +50,14 @@ module Diaspora
|
|||
end
|
||||
end
|
||||
|
||||
# Remote pods which are known to be subscribed to the post. Must include all pods which received the post in the
|
||||
# past.
|
||||
#
|
||||
# @return [Array<String>] The list of pods' URIs
|
||||
def subscribed_pods_uris
|
||||
Pod.find(subscribers.select(&:remote?).map(&:pod_id).uniq).map {|pod| pod.url_to("") }
|
||||
end
|
||||
|
||||
module QueryMethods
|
||||
def owned_or_visible_by_user(user)
|
||||
with_visibility.where(
|
||||
|
|
|
|||
255
lib/schemas/archive-format.json
Normal file
255
lib/schemas/archive-format.json
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"id": "https://diaspora.github.io/diaspora/schemas/archive_format.json",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"user": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": { "type": "string" },
|
||||
"language": { "type": "string" },
|
||||
"username": { "type": "string" },
|
||||
"private_key": { "type": "string" },
|
||||
"disable_mail": { "type": "boolean" },
|
||||
"show_community_spotlight_in_stream": { "type": "boolean" },
|
||||
"auto_follow_back": { "type": "boolean" },
|
||||
"auto_follow_back_aspect": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"strip_exif": { "type": "boolean" },
|
||||
|
||||
"profile": {
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#definitions/profile"
|
||||
},
|
||||
|
||||
"contact_groups": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"contacts_visible": { "type": "boolean" },
|
||||
"chat_enabled": { "type": "boolean" }
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"contacts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sharing": { "type": "boolean" },
|
||||
"following": { "type": "boolean" },
|
||||
"receiving": { "type": "boolean" },
|
||||
"followed": { "type": "boolean" },
|
||||
"account_id": { "type": "string" },
|
||||
"contact_groups_membership": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
},
|
||||
"person_name": { "type": "string" },
|
||||
"person_guid": {
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/guid"
|
||||
},
|
||||
"public_key": { "type": "string" }
|
||||
},
|
||||
"required": [
|
||||
"sharing",
|
||||
"following",
|
||||
"receiving",
|
||||
"followed",
|
||||
"account_id",
|
||||
"contact_groups_membership"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"posts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/posts"
|
||||
},
|
||||
{
|
||||
"oneOf": [
|
||||
{ "$ref": "#/definitions/remote_subscription/public" },
|
||||
{ "$ref": "#/definitions/remote_subscription/private" }
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
"relayables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/comment"
|
||||
},
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/like"
|
||||
},
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/poll_participation"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"followed_tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"post_subscriptions": {
|
||||
"type": "array",
|
||||
"description": "GUIDs of posts for which changes we want to be subscribed",
|
||||
"items": {
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/guid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"username",
|
||||
"email",
|
||||
"private_key",
|
||||
"profile"
|
||||
]
|
||||
},
|
||||
"others_data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"relayables": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/comment"
|
||||
},
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/like"
|
||||
},
|
||||
{
|
||||
"$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/poll_participation"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"non_contact_authors": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"guid": {
|
||||
"type": "string"
|
||||
},
|
||||
"public_key": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"account_id",
|
||||
"guid",
|
||||
"public_key"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
"posts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/posts"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^2\.0$"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"user",
|
||||
"version"
|
||||
],
|
||||
"definitions": {
|
||||
"posts": {
|
||||
"oneOf": [
|
||||
{ "$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/status_message" },
|
||||
{ "$ref": "https://diaspora.github.io/diaspora_federation/schemas/federation_entities.json#/definitions/reshare" }
|
||||
]
|
||||
},
|
||||
|
||||
"remote_subscription": {
|
||||
"public": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subscribed_pods_uris": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"entity_data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"public": {
|
||||
"enum": [ true ]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"public"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"entity_data"
|
||||
]
|
||||
},
|
||||
|
||||
"private": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subscribed_users_ids": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
||||
"entity_data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"public": {
|
||||
"enum": [ false ]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"public"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,9 +149,9 @@ FactoryGirl.define do
|
|||
end
|
||||
|
||||
factory(:location) do
|
||||
address "Fernsehturm Berlin, Berlin, Germany"
|
||||
lat 52.520645
|
||||
lng 13.409779
|
||||
sequence(:address) {|n| "Fernsehturm Berlin, #{n}, Berlin, Germany" }
|
||||
sequence(:lat) {|n| 52.520645 + 0.0000001 * n }
|
||||
sequence(:lng) {|n| 13.409779 + 0.0000001 * n }
|
||||
end
|
||||
|
||||
factory :participation do
|
||||
|
|
|
|||
444
spec/integration/exporter_spec.rb
Normal file
444
spec/integration/exporter_spec.rb
Normal file
|
|
@ -0,0 +1,444 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
describe Diaspora::Exporter do
|
||||
let(:user) { FactoryGirl.create(:user_with_aspect) }
|
||||
|
||||
context "output json" do
|
||||
let(:json) { Diaspora::Exporter.new(user).execute }
|
||||
|
||||
it "matches archive schema" do
|
||||
DataGenerator.create(
|
||||
user,
|
||||
%i[generic_user_data activity status_messages_flavours work_aspect]
|
||||
)
|
||||
|
||||
expect(JSON.parse(json)).to match_json_schema(:archive_schema)
|
||||
end
|
||||
|
||||
it "contains basic user data" do
|
||||
user_properties = build_property_hash(
|
||||
user,
|
||||
%i[email username language disable_mail show_community_spotlight_in_stream auto_follow_back
|
||||
auto_follow_back_aspect strip_exif],
|
||||
private_key: :serialized_private_key
|
||||
)
|
||||
|
||||
user_properties[:profile] = {
|
||||
entity_type: "profile",
|
||||
entity_data: build_property_hash(
|
||||
user.profile,
|
||||
%i[first_name last_name gender bio location image_url birthday searchable nsfw tag_string],
|
||||
author: :diaspora_handle
|
||||
)
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: user_properties)
|
||||
end
|
||||
|
||||
it "contains aspects" do
|
||||
DataGenerator.create(user, :work_aspect)
|
||||
|
||||
expect(json).to include_json(
|
||||
user: {
|
||||
"contact_groups": [
|
||||
{
|
||||
"name": "generic",
|
||||
"contacts_visible": true,
|
||||
"chat_enabled": false
|
||||
},
|
||||
{
|
||||
"name": "Work",
|
||||
"contacts_visible": false,
|
||||
"chat_enabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
it "contains contacts" do
|
||||
friends = DataGenerator.create(user, Array.new(2, :mutual_friend))
|
||||
serialized_contacts = friends.map {|friend|
|
||||
contact = Contact.find_by(person_id: friend.person_id)
|
||||
hash = build_property_hash(
|
||||
contact,
|
||||
%i[sharing receiving person_guid person_name],
|
||||
following: :sharing, followed: :receiving, account_id: :person_diaspora_handle
|
||||
)
|
||||
hash[:public_key] = contact.person.serialized_public_key
|
||||
hash[:contact_groups_membership] = contact.aspects.map(&:name)
|
||||
hash
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {contacts: serialized_contacts})
|
||||
end
|
||||
|
||||
it "contains a public status message" do
|
||||
status_message = FactoryGirl.create(:status_message, author: user.person, public: true)
|
||||
serialized = {
|
||||
"subscribed_pods_uris": [AppConfig.pod_uri.to_s],
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"public": true
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {posts: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a status message with subscribers" do
|
||||
subscriber, status_message = DataGenerator.create(user, :status_message_with_subscriber)
|
||||
serialized = {
|
||||
"subscribed_users_ids": [subscriber.diaspora_handle],
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {posts: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a status message with a poll" do
|
||||
status_message = FactoryGirl.create(:status_message_with_poll, author: user.person)
|
||||
serialized = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"poll": {
|
||||
"entity_type": "poll",
|
||||
"entity_data": {
|
||||
"guid": status_message.poll.guid,
|
||||
"question": status_message.poll.question,
|
||||
"poll_answers": status_message.poll.poll_answers.map {|answer|
|
||||
{
|
||||
"entity_type": "poll_answer",
|
||||
"entity_data": {
|
||||
"guid": answer.guid,
|
||||
"answer": answer.answer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {posts: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a status message with a photo" do
|
||||
status_message = FactoryGirl.create(:status_message_with_photo, author: user.person)
|
||||
|
||||
serialized = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"photos": [
|
||||
{
|
||||
"entity_type": "photo",
|
||||
"entity_data": {
|
||||
"guid": status_message.photos.first.guid,
|
||||
"author": user.diaspora_handle,
|
||||
"public": false,
|
||||
"created_at": status_message.photos.first.created_at.iso8601,
|
||||
"remote_photo_path": "#{AppConfig.pod_uri}uploads\/images\/",
|
||||
"remote_photo_name": status_message.photos.first.remote_photo_name,
|
||||
"status_message_guid": status_message.guid,
|
||||
"height": 42,
|
||||
"width": 23
|
||||
}
|
||||
}
|
||||
],
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {posts: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a status message with a location" do
|
||||
status_message = FactoryGirl.create(:status_message_with_location, author: user.person)
|
||||
|
||||
serialized = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"location": {
|
||||
"entity_type": "location",
|
||||
"entity_data": {
|
||||
"address": status_message.location.address,
|
||||
"lat": status_message.location.lat,
|
||||
"lng": status_message.location.lng
|
||||
}
|
||||
},
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(user: {posts: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a reshare and its root" do
|
||||
reshare = FactoryGirl.create(:reshare, author: user.person)
|
||||
serialized_reshare = {
|
||||
"subscribed_pods_uris": [reshare.root.author.pod.url_to(""), AppConfig.pod_uri.to_s],
|
||||
"entity_type": "reshare",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": reshare.guid,
|
||||
"created_at": reshare.created_at.iso8601,
|
||||
"public": true,
|
||||
"root_author": reshare.root_author.diaspora_handle,
|
||||
"root_guid": reshare.root_guid
|
||||
}
|
||||
}
|
||||
|
||||
status_message = reshare.root
|
||||
serialized_parent = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": status_message.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"public": true
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(
|
||||
user: {posts: [serialized_reshare]},
|
||||
others_data: {posts: [serialized_parent]}
|
||||
)
|
||||
end
|
||||
|
||||
it "contains followed tags" do
|
||||
tag_following = DataGenerator.create(user, :tag_following)
|
||||
expect(json).to include_json(user: {followed_tags: [tag_following.tag.name]})
|
||||
end
|
||||
|
||||
it "contains post subscriptions" do
|
||||
subscription = DataGenerator.create(user, :subscription)
|
||||
expect(json).to include_json(user: {post_subscriptions: [subscription.target.guid]})
|
||||
end
|
||||
|
||||
it "contains a comment and the commented post" do
|
||||
comment = FactoryGirl.create(:comment, author: user.person)
|
||||
serialized_comment = {
|
||||
"entity_type": "comment",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": comment.guid,
|
||||
"parent_guid": comment.parent.guid,
|
||||
"text": comment.text,
|
||||
"created_at": comment.created_at.iso8601
|
||||
},
|
||||
"property_order": %w[author guid parent_guid text created_at]
|
||||
}
|
||||
|
||||
status_message = comment.parent
|
||||
serialized_post = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": status_message.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(
|
||||
user: {relayables: [serialized_comment]},
|
||||
others_data: {posts: [serialized_post]}
|
||||
)
|
||||
end
|
||||
|
||||
it "contains a like and the liked post" do
|
||||
like = FactoryGirl.create(:like, author: user.person)
|
||||
serialized_like = {
|
||||
"entity_type": "like",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": like.guid,
|
||||
"parent_guid": like.parent.guid,
|
||||
"parent_type": like.target_type,
|
||||
"positive": like.positive
|
||||
},
|
||||
"property_order": %w[author guid parent_guid parent_type positive]
|
||||
}
|
||||
|
||||
status_message = like.target
|
||||
serialized_post = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": status_message.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(
|
||||
user: {relayables: [serialized_like]},
|
||||
others_data: {posts: [serialized_post]}
|
||||
)
|
||||
end
|
||||
|
||||
it "contains a poll participation and post with this poll" do
|
||||
poll_participation = FactoryGirl.create(:poll_participation, author: user.person)
|
||||
serialized_participation = {
|
||||
"entity_type": "poll_participation",
|
||||
"entity_data": {
|
||||
"author": user.diaspora_handle,
|
||||
"guid": poll_participation.guid,
|
||||
"parent_guid": poll_participation.parent.guid,
|
||||
"poll_answer_guid": poll_participation.poll_answer.guid
|
||||
},
|
||||
"property_order": %w[author guid parent_guid poll_answer_guid]
|
||||
}
|
||||
|
||||
poll = poll_participation.poll
|
||||
status_message = poll_participation.status_message
|
||||
serialized_post = {
|
||||
"entity_type": "status_message",
|
||||
"entity_data": {
|
||||
"author": status_message.diaspora_handle,
|
||||
"guid": status_message.guid,
|
||||
"created_at": status_message.created_at.iso8601,
|
||||
"text": status_message.text,
|
||||
"poll": {
|
||||
"entity_type": "poll",
|
||||
"entity_data": {
|
||||
"guid": poll.guid,
|
||||
"question": poll.question,
|
||||
"poll_answers": poll.poll_answers.map {|answer|
|
||||
{
|
||||
"entity_type": "poll_answer",
|
||||
"entity_data": {
|
||||
"guid": answer.guid,
|
||||
"answer": answer.answer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
||||
expect(json).to include_json(
|
||||
user: {relayables: [serialized_participation]},
|
||||
others_data: {posts: [serialized_post]}
|
||||
)
|
||||
end
|
||||
|
||||
it "contains a comment for the user's post" do
|
||||
status_message, comment = DataGenerator.create(user, :status_message_with_comment)
|
||||
serialized = {
|
||||
"entity_type": "comment",
|
||||
"entity_data": {
|
||||
"author": comment.diaspora_handle,
|
||||
"guid": comment.guid,
|
||||
"parent_guid": status_message.guid,
|
||||
"text": comment.text,
|
||||
"created_at": comment.created_at.iso8601,
|
||||
"author_signature": Diaspora::Federation::Entities.build(comment).to_h[:author_signature]
|
||||
},
|
||||
"property_order": %w[author guid parent_guid text created_at]
|
||||
}
|
||||
|
||||
expect(json).to include_json(others_data: {relayables: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a like for the user's post" do
|
||||
status_message, like = DataGenerator.create(user, :status_message_with_like)
|
||||
serialized = {
|
||||
"entity_type": "like",
|
||||
"entity_data": {
|
||||
"author": like.diaspora_handle,
|
||||
"guid": like.guid,
|
||||
"parent_guid": status_message.guid,
|
||||
"parent_type": like.target_type,
|
||||
"positive": like.positive,
|
||||
"author_signature": Diaspora::Federation::Entities.build(like).to_h[:author_signature]
|
||||
},
|
||||
"property_order": %w[author guid parent_guid parent_type positive]
|
||||
}
|
||||
|
||||
expect(json).to include_json(others_data: {relayables: [serialized]})
|
||||
end
|
||||
|
||||
it "contains a poll participation for the user's post" do
|
||||
_, poll_participation = DataGenerator.create(user, :status_message_with_poll_participation)
|
||||
serialized = {
|
||||
"entity_type": "poll_participation",
|
||||
"entity_data": {
|
||||
"author": poll_participation.diaspora_handle,
|
||||
"guid": poll_participation.guid,
|
||||
"parent_guid": poll_participation.parent.guid,
|
||||
"poll_answer_guid": poll_participation.poll_answer.guid,
|
||||
"author_signature": Diaspora::Federation::Entities.build(poll_participation).to_h[:author_signature]
|
||||
},
|
||||
"property_order": %w[author guid parent_guid poll_answer_guid]
|
||||
}
|
||||
|
||||
expect(json).to include_json(others_data: {relayables: [serialized]})
|
||||
end
|
||||
|
||||
it "contains metadata of a non-contact author of a post where we commented" do
|
||||
comment = FactoryGirl.create(:comment, author: user.person)
|
||||
|
||||
author = comment.parent.author
|
||||
expect(json).to include_json(
|
||||
others_data: {
|
||||
non_contact_authors: [
|
||||
{
|
||||
"guid": author.guid,
|
||||
"account_id": author.diaspora_handle,
|
||||
"public_key": author.serialized_public_key
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def transform_value(value)
|
||||
return value.iso8601 if value.is_a? Date
|
||||
value
|
||||
end
|
||||
|
||||
def build_property_hash(object, direct_properties, aliased_properties={})
|
||||
props = direct_properties.map {|key|
|
||||
[key, transform_value(object.send(key))]
|
||||
}.to_h
|
||||
|
||||
aliased = aliased_properties.map {|key, key_alias|
|
||||
[key, object.send(key_alias)]
|
||||
}.to_h
|
||||
|
||||
props.merge(aliased)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,8 @@ describe Diaspora::Exporter::PostsWithActivity do
|
|||
user.person.likes.first.target,
|
||||
user.person.comments.first.parent,
|
||||
user.person.poll_participations.first.parent.status_message,
|
||||
user.person.participations.first.target
|
||||
user.person.participations.first.target,
|
||||
user.person.posts.reshares.first.root
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,86 +1,15 @@
|
|||
# Copyright (c) 2010-2011, Diaspora Inc. This file is
|
||||
# licensed under the Affero General Public License version 3 or later. See
|
||||
# the COPYRIGHT file.
|
||||
|
||||
require Rails.root.join('lib', 'diaspora', 'exporter')
|
||||
|
||||
describe Diaspora::Exporter do
|
||||
describe "#execute" do
|
||||
it "calls exporters and forms JSON" do
|
||||
expect_any_instance_of(Export::UserSerializer).to receive(:as_json).and_return(user: "user_data")
|
||||
expect_any_instance_of(Export::OthersDataSerializer).to receive(:as_json).and_return(others_date: "others_data")
|
||||
|
||||
before do
|
||||
@user1 = alice
|
||||
|
||||
@user1.person.profile.first_name = "<script>"
|
||||
@user1.person.profile.gender = "<script>"
|
||||
@user1.person.profile.bio = "<script>"
|
||||
@user1.person.profile.location = "<script>"
|
||||
@user1.person.profile.save
|
||||
|
||||
@aspect = @user1.aspects.first
|
||||
@aspect1 = @user1.aspects.create(:name => "Work", :contacts_visible => false)
|
||||
@aspect.name = "<script>"
|
||||
@aspect.save
|
||||
end
|
||||
|
||||
context "json" do
|
||||
|
||||
def json
|
||||
@json ||= JSON.parse Diaspora::Exporter.new(@user1).execute
|
||||
json = Diaspora::Exporter.new(nil).execute
|
||||
expect(json).to include_json(
|
||||
version: "2.0",
|
||||
user: "user_data",
|
||||
others_date: "others_data"
|
||||
)
|
||||
end
|
||||
|
||||
it { matches :version, to: '1.0' }
|
||||
it { matches :user, :name }
|
||||
it { matches :user, :email }
|
||||
it { matches :user, :username }
|
||||
it { matches :user, :language }
|
||||
it { matches :user, :disable_mail }
|
||||
it { matches :user, :show_community_spotlight_in_stream }
|
||||
it { matches :user, :auto_follow_back }
|
||||
it { matches :user, :auto_follow_back_aspect }
|
||||
it { matches :user, :strip_exif }
|
||||
|
||||
it { matches :user, :profile, :first_name, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :last_name, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :gender, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :bio, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :location, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :image_url, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :diaspora_handle, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :searchable, root: @user1.person.profile }
|
||||
it { matches :user, :profile, :nsfw, root: @user1.person.profile }
|
||||
|
||||
it { matches_relation :aspects, :name,
|
||||
:contacts_visible,
|
||||
:chat_enabled }
|
||||
|
||||
it { matches_relation :contacts, :sharing,
|
||||
:receiving,
|
||||
:person_guid,
|
||||
:person_name,
|
||||
:person_first_name,
|
||||
:person_diaspora_handle }
|
||||
|
||||
private
|
||||
|
||||
def matches(*fields, to: nil, root: @user1)
|
||||
expected = to || root.send(fields.last)
|
||||
expect(recurse_field(json, fields)).to eq expected
|
||||
end
|
||||
|
||||
def matches_relation(relation, *fields, to: nil, root: @user1)
|
||||
array = json['user'][to || relation.to_s]
|
||||
fields.each do |field|
|
||||
expected = root.send(relation).map(&:"#{field}")
|
||||
expect(array.map { |f| f[field.to_s] }).to eq expected
|
||||
end
|
||||
end
|
||||
|
||||
def recurse_field(json, fields)
|
||||
if fields.any?
|
||||
recurse_field json[fields.shift.to_s], fields
|
||||
else
|
||||
json
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,10 @@ describe StatusMessage, type: :model do
|
|||
let!(:aspect) { user.aspects.first }
|
||||
let(:status) { build(:status_message) }
|
||||
|
||||
it_behaves_like "a shareable" do
|
||||
let(:object) { status }
|
||||
end
|
||||
|
||||
describe "scopes" do
|
||||
describe ".where_person_is_mentioned" do
|
||||
it "returns status messages where the given person is mentioned" do
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
describe Export::CommentSerializer do
|
||||
let(:comment) { create(:comment) }
|
||||
subject(:json_output) { Export::CommentSerializer.new(comment).to_json }
|
||||
|
||||
it { is_expected.to include %("guid":"#{comment.guid}") }
|
||||
it { is_expected.to include %("post_guid":"#{comment.post.guid}") }
|
||||
it { is_expected.to include %("text":"#{comment.text}") }
|
||||
end
|
||||
12
spec/serializers/export/aspect_serializer_spec.rb
Normal file
12
spec/serializers/export/aspect_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
describe Export::AspectSerializer do
|
||||
let(:aspect) { FactoryGirl.create(:aspect) }
|
||||
let(:serializer) { Export::AspectSerializer.new(aspect) }
|
||||
|
||||
it "has aspect attributes" do
|
||||
expect(serializer.attributes).to eq(
|
||||
name: aspect.name,
|
||||
contacts_visible: aspect.contacts_visible,
|
||||
chat_enabled: aspect.chat_enabled
|
||||
)
|
||||
end
|
||||
end
|
||||
25
spec/serializers/export/contact_serializer_spec.rb
Normal file
25
spec/serializers/export/contact_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
describe Export::ContactSerializer do
|
||||
let(:contact) { FactoryGirl.create(:contact) }
|
||||
let(:serializer) { Export::ContactSerializer.new(contact) }
|
||||
let(:aspect) { FactoryGirl.create(:aspect) }
|
||||
|
||||
it "has contact attributes" do
|
||||
expect(serializer.attributes).to eq(
|
||||
sharing: contact.sharing,
|
||||
following: contact.sharing,
|
||||
receiving: contact.receiving,
|
||||
followed: contact.receiving,
|
||||
person_guid: contact.person_guid,
|
||||
person_name: contact.person_name,
|
||||
account_id: contact.person_diaspora_handle,
|
||||
public_key: contact.person.serialized_public_key
|
||||
)
|
||||
end
|
||||
|
||||
it "serializes aspects membership" do
|
||||
contact.aspects << aspect
|
||||
expect(Export::ContactSerializer).to serialize_association(:contact_groups_membership)
|
||||
.with_objects(contact.aspects.map(&:name))
|
||||
expect(serializer.associations[:contact_groups_membership]).to eq([aspect.name])
|
||||
end
|
||||
end
|
||||
43
spec/serializers/export/others_data_serializer_spec.rb
Normal file
43
spec/serializers/export/others_data_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
describe Export::OthersDataSerializer do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:serializer) { Export::OthersDataSerializer.new(user) }
|
||||
let(:others_posts) {
|
||||
[
|
||||
*user.person.likes.map(&:target),
|
||||
*user.person.comments.map(&:parent),
|
||||
*user.person.posts.reshares.map(&:root),
|
||||
*user.person.poll_participations.map(&:status_message)
|
||||
]
|
||||
}
|
||||
|
||||
it "uses FederationEntitySerializer for array serializing relayables" do
|
||||
sm = DataGenerator.new(user).status_message_with_activity
|
||||
|
||||
expect(Export::OthersDataSerializer).to serialize_association(:relayables)
|
||||
.with_each_serializer(FederationEntitySerializer)
|
||||
.with_objects([*sm.likes, *sm.comments, *sm.poll_participations])
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
context "with user's activity" do
|
||||
before do
|
||||
DataGenerator.new(user).activity
|
||||
end
|
||||
|
||||
it "uses FederationEntitySerializer for array serializing posts" do
|
||||
expect(Export::OthersDataSerializer).to serialize_association(:posts)
|
||||
.with_each_serializer(FederationEntitySerializer)
|
||||
.with_objects(others_posts)
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "uses PersonMetadataSerializer for array serializing non_contact_authors" do
|
||||
non_contact_authors = others_posts.map(&:author)
|
||||
|
||||
expect(Export::OthersDataSerializer).to serialize_association(:non_contact_authors)
|
||||
.with_each_serializer(Export::PersonMetadataSerializer)
|
||||
.with_objects(non_contact_authors)
|
||||
serializer.associations
|
||||
end
|
||||
end
|
||||
end
|
||||
48
spec/serializers/export/own_post_serializer_spec.rb
Normal file
48
spec/serializers/export/own_post_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
describe Export::OwnPostSerializer do
|
||||
let(:author) { FactoryGirl.create(:user_with_aspect).person }
|
||||
|
||||
before do
|
||||
author.owner.share_with(FactoryGirl.create(:person), author.owner.aspects.first)
|
||||
end
|
||||
|
||||
it_behaves_like "a federation entity serializer" do
|
||||
let(:object) { create(:status_message_with_photo, author: author) }
|
||||
end
|
||||
|
||||
let(:json) { Export::OwnPostSerializer.new(post, root: false).to_json }
|
||||
|
||||
context "with private post" do
|
||||
let(:post) { create(:status_message_in_aspect, author: author) }
|
||||
|
||||
it "includes remote people subscriptions" do
|
||||
expect(JSON.parse(json)["subscribed_users_ids"]).not_to be_empty
|
||||
expect(json).to include_json(subscribed_users_ids: post.subscribers.map(&:diaspora_handle))
|
||||
end
|
||||
|
||||
it "doesn't include remote pods subscriptions" do
|
||||
expect(JSON.parse(json)).not_to have_key("subscribed_pods_uris")
|
||||
end
|
||||
end
|
||||
|
||||
context "with public post" do
|
||||
let(:post) {
|
||||
FactoryGirl.create(
|
||||
:status_message_with_participations,
|
||||
author: author,
|
||||
participants: Array.new(2) { FactoryGirl.create(:person) },
|
||||
public: true
|
||||
)
|
||||
}
|
||||
|
||||
it "includes pods subscriptions" do
|
||||
expect(JSON.parse(json)["subscribed_pods_uris"]).not_to be_empty
|
||||
expect(json).to include_json(
|
||||
subscribed_pods_uris: post.subscribed_pods_uris.push(AppConfig.pod_uri.to_s)
|
||||
)
|
||||
end
|
||||
|
||||
it "doesn't include remote people subscriptions" do
|
||||
expect(JSON.parse(json)).not_to have_key("subscribed_users_ids")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
describe Export::OwnRelayablesSerializer do
|
||||
let(:comment) { FactoryGirl.create(:comment) }
|
||||
let!(:signature) { FactoryGirl.create(:comment_signature, comment: comment) }
|
||||
let(:instance) { Export::OwnRelayablesSerializer.new(comment, root: false) }
|
||||
|
||||
it "doesn't include author signature to the entity data" do
|
||||
expect(JSON.parse(instance.to_json)["entity_data"]).not_to have_key("author_signature")
|
||||
end
|
||||
end
|
||||
12
spec/serializers/export/person_metadata_serializer_spec.rb
Normal file
12
spec/serializers/export/person_metadata_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
describe Export::PersonMetadataSerializer do
|
||||
let(:person) { FactoryGirl.create(:person) }
|
||||
let(:serializer) { Export::PersonMetadataSerializer.new(person) }
|
||||
|
||||
it "has person metadata attributes" do
|
||||
expect(serializer.attributes).to eq(
|
||||
guid: person.guid,
|
||||
account_id: person.diaspora_handle,
|
||||
public_key: person.serialized_public_key
|
||||
)
|
||||
end
|
||||
end
|
||||
79
spec/serializers/export/user_serializer_spec.rb
Normal file
79
spec/serializers/export/user_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
describe Export::UserSerializer do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:serializer) { Export::UserSerializer.new(user, root: false) }
|
||||
|
||||
it "has basic user's attributes" do
|
||||
expect(serializer.attributes).to eq(
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
language: user.language,
|
||||
private_key: user.serialized_private_key,
|
||||
disable_mail: user.disable_mail,
|
||||
show_community_spotlight_in_stream: user.show_community_spotlight_in_stream,
|
||||
auto_follow_back: user.auto_follow_back,
|
||||
auto_follow_back_aspect: user.auto_follow_back_aspect,
|
||||
strip_exif: user.strip_exif
|
||||
)
|
||||
end
|
||||
|
||||
it "uses FederationEntitySerializer to serialize user profile" do
|
||||
expect(Export::UserSerializer).to serialize_association(:profile)
|
||||
.with_serializer(FederationEntitySerializer)
|
||||
.with_object(user.profile)
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "uses AspectSerializer for array serializing contact_groups" do
|
||||
DataGenerator.create(user, %i[first_aspect work_aspect])
|
||||
expect(Export::UserSerializer).to serialize_association(:contact_groups)
|
||||
.with_each_serializer(Export::AspectSerializer)
|
||||
.with_objects([user.aspects.first, user.aspects.second])
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "uses ContactSerializer for array serializing contacts" do
|
||||
DataGenerator.create(user, %i[mutual_friend mutual_friend])
|
||||
expect(Export::UserSerializer).to serialize_association(:contacts)
|
||||
.with_each_serializer(Export::ContactSerializer)
|
||||
.with_objects([user.contacts.first, user.contacts.second])
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "uses OwnPostSerializer for array serializing posts" do
|
||||
DataGenerator.create(user, %i[public_status_message private_status_message])
|
||||
expect(Export::UserSerializer).to serialize_association(:posts)
|
||||
.with_each_serializer(Export::OwnPostSerializer)
|
||||
.with_objects([user.posts.first, user.posts.second])
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "serializes followed tags" do
|
||||
DataGenerator.create(user, %i[tag_following tag_following])
|
||||
expect(Export::UserSerializer).to serialize_association(:followed_tags)
|
||||
.with_objects([user.followed_tags.first.name, user.followed_tags.second.name])
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "uses OwnRelayablesSerializer for array serializing relayables" do
|
||||
DataGenerator.create(user, :activity)
|
||||
|
||||
objects = %i[comments likes poll_participations].map do |association|
|
||||
user.person.send(association).first
|
||||
end
|
||||
|
||||
expect(Export::UserSerializer).to serialize_association(:relayables)
|
||||
.with_each_serializer(Export::OwnRelayablesSerializer)
|
||||
.with_objects(objects)
|
||||
serializer.associations
|
||||
end
|
||||
|
||||
it "serializes post subscriptions" do
|
||||
DataGenerator.create(user, %i[participation participation])
|
||||
subscriptions = user.person.participations.map do |participation|
|
||||
participation.target.guid
|
||||
end
|
||||
expect(Export::UserSerializer).to serialize_association(:post_subscriptions)
|
||||
.with_objects(subscriptions)
|
||||
serializer.associations
|
||||
end
|
||||
end
|
||||
17
spec/serializers/federation_entity_serializer_spec.rb
Normal file
17
spec/serializers/federation_entity_serializer_spec.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
describe FederationEntitySerializer do
|
||||
class TestEntity < DiasporaFederation::Entity
|
||||
property :test, :string
|
||||
end
|
||||
|
||||
let(:object) { double }
|
||||
|
||||
before do
|
||||
# Mock a builder for a TestEntity that we define for this test
|
||||
allow(Diaspora::Federation::Mappings).to receive(:builder_for).with(object.class).and_return(:test_entity)
|
||||
allow(Diaspora::Federation::Entities).to receive(:test_entity).with(object) {
|
||||
TestEntity.new(test: "asdf")
|
||||
}
|
||||
end
|
||||
|
||||
it_behaves_like "a federation entity serializer"
|
||||
end
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
describe Export::PostSerializer do
|
||||
let(:post) { create(:status_message_with_photo) }
|
||||
subject(:json_output) { Export::PostSerializer.new(post).to_json }
|
||||
|
||||
it { is_expected.to include %("guid":"#{post.guid}") }
|
||||
it { is_expected.to include %("text":"#{post.text}") }
|
||||
it { is_expected.to include %("public":#{post.public}) }
|
||||
it { is_expected.to include %("diaspora_handle":"#{post.diaspora_handle}") }
|
||||
it { is_expected.to include %("type":"#{post.type}") }
|
||||
it { is_expected.to include %("likes_count":#{post.likes_count}) }
|
||||
it { is_expected.to include %("comments_count":#{post.comments_count}) }
|
||||
it { is_expected.to include %("reshares_count":#{post.reshares_count}) }
|
||||
it { is_expected.to include %("created_at":"#{post.created_at.to_s[0, 4]}) }
|
||||
it { is_expected.to include %("created_at":"#{post.created_at.strftime('%FT%T.%LZ')}) }
|
||||
end
|
||||
33
spec/serializers/serializer_post_processing_spec.rb
Normal file
33
spec/serializers/serializer_post_processing_spec.rb
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
describe SerializerPostProcessing do
|
||||
describe "#modify_serializable_object" do
|
||||
it "allows to modify serializable object of ActiveModel::Serializer ancestor" do
|
||||
class TestSerializer < ActiveModel::Serializer
|
||||
include SerializerPostProcessing
|
||||
|
||||
def modify_serializable_object(*)
|
||||
{
|
||||
custom_key: "custom_value"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
serializer = TestSerializer.new({}, root: false)
|
||||
expect(serializer).to receive(:modify_serializable_object).and_call_original
|
||||
expect(serializer.to_json).to eq("{\"custom_key\":\"custom_value\"}")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#except" do
|
||||
it "allows to except a key from attributes" do
|
||||
class TestSerializer2 < ActiveModel::Serializer
|
||||
include SerializerPostProcessing
|
||||
|
||||
attributes :key_to_exclude
|
||||
end
|
||||
|
||||
serializer = TestSerializer2.new({}, root: false)
|
||||
serializer.except = [:key_to_exclude]
|
||||
expect(serializer.to_json).to eq("{}")
|
||||
end
|
||||
end
|
||||
end
|
||||
14
spec/shared_behaviors/federation_entity_serializer.rb
Normal file
14
spec/shared_behaviors/federation_entity_serializer.rb
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
shared_examples_for "a federation entity serializer" do
|
||||
describe "#to_json" do
|
||||
it "contains JSON serialized entity object" do
|
||||
entity = nil
|
||||
expect(Diaspora::Federation::Entities).to receive(:build)
|
||||
.with(object)
|
||||
.and_wrap_original do |original, object, &block|
|
||||
entity = original.call(object, &block)
|
||||
end
|
||||
json = described_class.new(object, root: false).to_json
|
||||
expect(json).to include_json(entity.to_json)
|
||||
end
|
||||
end
|
||||
end
|
||||
18
spec/shared_behaviors/shareable.rb
Normal file
18
spec/shared_behaviors/shareable.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
shared_examples_for "a shareable" do
|
||||
describe "#subscribed_pods_uris" do
|
||||
let(:pods) { Array.new(3) { FactoryGirl.create(:pod) } }
|
||||
let(:subscribers) {
|
||||
pods.map {|pod|
|
||||
FactoryGirl.create(:person, pod: pod)
|
||||
}
|
||||
}
|
||||
let(:pods_uris) {
|
||||
pods.map {|pod| pod.url_to("") }
|
||||
}
|
||||
|
||||
it "builds pod list basing on subscribers" do
|
||||
expect(object).to receive(:subscribers).and_return(subscribers)
|
||||
expect(object.subscribed_pods_uris).to match_array(pods_uris)
|
||||
end
|
||||
end
|
||||
end
|
||||
57
spec/spec/data_generator_spec.rb
Normal file
57
spec/spec/data_generator_spec.rb
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
RSpec::Matchers.define :have_subscribers do
|
||||
match do |posts|
|
||||
posts.map(&:subscribers).delete_if(&:empty?).any?
|
||||
end
|
||||
end
|
||||
|
||||
# verifications of data generation (protect us from possible false positives in case of poor data preset)
|
||||
describe DataGenerator do
|
||||
let(:user) { FactoryGirl.create(:user) }
|
||||
let(:generator) { DataGenerator.new(user) }
|
||||
|
||||
describe "#generic_user_data" do
|
||||
it "creates different data for user" do
|
||||
generator.generic_user_data
|
||||
expect(user.aspects).not_to be_empty
|
||||
expect(Post.subscribed_by(user)).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#status_messages_flavours" do
|
||||
let(:user) { FactoryGirl.create(:user_with_aspect) }
|
||||
|
||||
it "creates posts of different types" do
|
||||
expect(generator).to receive(:status_message_with_activity).and_call_original
|
||||
generator.status_messages_flavours
|
||||
expect(user.posts).not_to be_empty
|
||||
expect(user.posts.where(public: true)).to have_subscribers
|
||||
expect(user.posts.where(public: false)).to have_subscribers
|
||||
end
|
||||
end
|
||||
|
||||
describe "#status_message_with_activity" do
|
||||
it "creates a status message where presented all possible types of activity" do
|
||||
status_message = generator.status_message_with_activity
|
||||
expect(status_message.likes).not_to be_empty
|
||||
expect(status_message.comments).not_to be_empty
|
||||
expect(status_message.poll_participations).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#activity" do
|
||||
it "creates activity of different kinds" do
|
||||
generator.activity
|
||||
expect(user.posts.reshares).not_to be_empty
|
||||
expect(user.person.likes).not_to be_empty
|
||||
expect(user.person.comments).not_to be_empty
|
||||
expect(user.person.poll_participations).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe "#status_message_with_subscriber" do
|
||||
it "creates a status message with a subscriber" do
|
||||
subscriber, status_message = DataGenerator.create(user, :status_message_with_subscriber)
|
||||
expect(status_message.subscribers).to eq([subscriber.person])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -15,6 +15,7 @@ require "webmock/rspec"
|
|||
require "factory_girl"
|
||||
require "sidekiq/testing"
|
||||
require "shoulda/matchers"
|
||||
require "diaspora_federation/schemas"
|
||||
|
||||
include HelperMethods
|
||||
|
||||
|
|
@ -133,6 +134,16 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
config.include FactoryGirl::Syntax::Methods
|
||||
|
||||
config.include JSON::SchemaMatchers
|
||||
config.json_schemas[:archive_schema] = "lib/schemas/archive-format.json"
|
||||
|
||||
JSON::Validator.add_schema(
|
||||
JSON::Schema.new(
|
||||
DiasporaFederation::Schemas.federation_entities,
|
||||
Addressable::URI.parse(DiasporaFederation::Schemas::FEDERATION_ENTITIES_URI)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
Shoulda::Matchers.configure do |config|
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# TODO: docs
|
||||
# This is a helper class for tests that is capable of generating different sets of data, which are possibly
|
||||
# interrelated.
|
||||
class DataGenerator
|
||||
def person
|
||||
@person || user.person
|
||||
|
|
@ -23,7 +24,7 @@ class DataGenerator
|
|||
if type.is_a? Symbol
|
||||
generator.send(type)
|
||||
elsif type.is_a? Array
|
||||
type.each {|type|
|
||||
type.map {|type|
|
||||
generator.send(type)
|
||||
}
|
||||
end
|
||||
|
|
@ -100,23 +101,83 @@ class DataGenerator
|
|||
user.aspects.first || FactoryGirl.create(:aspect, user: user)
|
||||
end
|
||||
|
||||
def public_status_message
|
||||
FactoryGirl.create(:status_message, author: person, public: true)
|
||||
end
|
||||
|
||||
def private_status_message
|
||||
post = FactoryGirl.create(:status_message, author: person)
|
||||
|
||||
person.contacts.each do |contact|
|
||||
ShareVisibility.create!(user_id: contact.user.id, shareable: post)
|
||||
ShareVisibility.create!(user_id: contact.user_id, shareable: post)
|
||||
end
|
||||
end
|
||||
|
||||
%i(photo participation).each do |factory|
|
||||
%i[photo participation status_message_with_location status_message_with_poll status_message_with_photo
|
||||
status_message status_message_in_aspect reshare like comment poll_participation].each do |factory|
|
||||
define_method factory do
|
||||
FactoryGirl.create(factory, author: person)
|
||||
end
|
||||
end
|
||||
|
||||
alias subscription participation
|
||||
|
||||
%i[mention role].each do |factory|
|
||||
define_method factory do
|
||||
FactoryGirl.create(factory, person: person)
|
||||
end
|
||||
end
|
||||
|
||||
def status_message_with_activity
|
||||
status_message_with_poll.tap {|post|
|
||||
mutual_friend.like!(post)
|
||||
mutual_friend.comment!(post, "1")
|
||||
mutual_friend.participate_in_poll!(post, post.poll.poll_answers.first)
|
||||
}
|
||||
end
|
||||
|
||||
def status_message_with_comment
|
||||
post = status_message_in_aspect
|
||||
[post, mutual_friend.comment!(post, "some text")]
|
||||
end
|
||||
|
||||
def status_message_with_like
|
||||
post = status_message_in_aspect
|
||||
[post, mutual_friend.like!(post)]
|
||||
end
|
||||
|
||||
def status_message_with_poll_participation
|
||||
post = status_message_with_poll
|
||||
[
|
||||
post,
|
||||
mutual_friend.participate_in_poll!(post, post.poll.poll_answers.first)
|
||||
]
|
||||
end
|
||||
|
||||
def activity
|
||||
reshare
|
||||
like
|
||||
comment
|
||||
poll_participation
|
||||
end
|
||||
|
||||
def work_aspect
|
||||
user.aspects.create(name: "Work", contacts_visible: false)
|
||||
end
|
||||
|
||||
def status_messages_flavours
|
||||
public_status_message
|
||||
status_message_with_location
|
||||
status_message_with_activity
|
||||
status_message_with_photo
|
||||
status_message_with_poll
|
||||
status_message_in_aspect
|
||||
end
|
||||
|
||||
def status_message_with_subscriber
|
||||
[
|
||||
mutual_friend,
|
||||
status_message_in_aspect
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
79
spec/support/serializer_matchers.rb
Normal file
79
spec/support/serializer_matchers.rb
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# This file contains custom RSpec matchers for AMS.
|
||||
# NOTE: It was developed for AMS v0.9 and the API might be changed in future, so that should be examined when moving
|
||||
# between stable versions of AMS (e.g. 0.9 to 0.10, or 0.9 to a possible 1.0).
|
||||
|
||||
# This is a matcher that tests a ActiveModel Serializer derivatives to run a serializer for
|
||||
# an association with expected properties and data.
|
||||
#
|
||||
# This matcher is expected to be used in unit testing of ActiveModel Serializer derivatives.
|
||||
#
|
||||
# It is mostly a wrapper around RSpec::Mocks::Matchers::Receive matcher with expectations based on
|
||||
# ActiveModel::Serializer internal API knowledge.
|
||||
# NOTE: this matcher uses knowledge of AMS internals
|
||||
RSpec::Matchers.define :serialize_association do |association_name|
|
||||
match do |root_serializer_class|
|
||||
association = fetch_association(root_serializer_class, association_name)
|
||||
execute_receive_matcher_with(association)
|
||||
end
|
||||
|
||||
# Sets expectation for a specific serializer class to be used for has_one association serialization
|
||||
chain :with_serializer, :association_serializer_class
|
||||
|
||||
# Sets expectation for a specific serializer class to be user for has_many association serialization
|
||||
chain :with_each_serializer, :each_serializer_class
|
||||
|
||||
# Sets expectation for actual data to be passed to the serializer of the association
|
||||
chain :with_object, :association_object
|
||||
alias_method :with_objects, :with_object
|
||||
|
||||
private
|
||||
|
||||
# subject is what comes from the expect method argument in usual notation.
|
||||
# So this method is equivalent of calling `expect(subject).to receive(:build_serializer)` but valid within a custom
|
||||
# RSpec matcher's match method.
|
||||
def execute_receive_matcher_with(subject)
|
||||
receive_matcher.matches?(subject)
|
||||
end
|
||||
|
||||
def receive_matcher
|
||||
receive(:build_serializer).and_wrap_original do |original, object, options, &block|
|
||||
with_object_expectation(object)
|
||||
|
||||
original.call(object, options, &block).tap do |serializer|
|
||||
expect(serializer).to be_an_instance_of(serializer_class) unless serializer_class.nil?
|
||||
expect(serializer).to serialize_each_with(each_serializer_class) unless each_serializer_class.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def with_object_expectation(object)
|
||||
if association_object.is_a?(Array)
|
||||
expect(object).to match_array(association_object)
|
||||
elsif !association_object.nil?
|
||||
expect(object).to eq(association_object)
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_association(serializer_class, association_name)
|
||||
serializer_class._associations[association_name]
|
||||
end
|
||||
|
||||
def serializer_class
|
||||
@serializer_class ||= pick_serializer_class
|
||||
end
|
||||
|
||||
def pick_serializer_class
|
||||
return association_serializer_class unless association_serializer_class.nil?
|
||||
return ActiveModel::ArraySerializer unless each_serializer_class.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# This serializer tests that ActiveModel::ArraySerializer uses specific serializer for each member object of the array.
|
||||
# We could also set a mock expectation on each serializer, but it maybe overly complicated for our present
|
||||
# requirements.
|
||||
# NOTE: this matcher uses knowledge of AMS internals
|
||||
RSpec::Matchers.define :serialize_each_with do |expected|
|
||||
match do |actual|
|
||||
actual.is_a?(ActiveModel::ArraySerializer) && actual.instance_variable_get("@each_serializer") == expected
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue