diff --git a/.rubocop.yml b/.rubocop.yml index f005830..bf92819 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,4 +19,7 @@ Metrics/MethodLength: Max: 13 Metrics/BlockLength: - Max: 80 + Max: 120 + +Metrics/AbcSize: + Max: 20 diff --git a/lib/activitypub/activity.rb b/lib/activitypub/activity.rb index 6ffc492..24bf872 100644 --- a/lib/activitypub/activity.rb +++ b/lib/activitypub/activity.rb @@ -8,7 +8,8 @@ module ActivityPub class Activity attr_accessor :id, :type, :actor, :object, :target, :published, :to, :cc, :bcc, :context - # Initializes a new Activity instance. + KNOWN_TYPES = %w[Create Update Delete Follow Accept Reject Add Remove Block Like].freeze + def initialize(attributes = {}) @id = attributes[:id] @type = attributes[:type] @@ -20,14 +21,40 @@ def initialize(attributes = {}) @cc = attributes[:cc] @bcc = attributes[:bcc] @context = attributes[:context] + + validate! + end + + def validate! + validate_required_fields! + validate_known_types! + validate_url_format!(@id) + validate_url_format!(@actor) + validate_url_format!(@object) + validate_url_format!(@target) + validate_timestamp_format!(@published) + end + + def validate_required_fields! + raise "Required field 'type' is missing" unless @type + raise "Required field 'actor' is missing" unless @actor + raise "Required field 'object' is missing" unless @object + end + + def validate_known_types! + raise "'#{@type}' is not a recognized activity type" unless KNOWN_TYPES.include?(@type) + end + + def validate_url_format!(url) + raise "Invalid URL format: '#{url}'" unless url.nil? || url.match?(URI::DEFAULT_PARSER.make_regexp) end - # Validate the activity attributes. - def valid? - validate_type && validate_actor && validate_object + def validate_timestamp_format!(timestamp) + Time.iso8601(timestamp) + rescue ArgumentError + raise "Invalid timestamp format: '#{timestamp}'" end - # Convert the Activity object into a hash representation. def to_h { id: @id, @@ -43,7 +70,6 @@ def to_h } end - # Generate an Activity object from a given hash. def self.from_h(hash) Activity.new( id: hash["id"], @@ -58,27 +84,6 @@ def self.from_h(hash) context: hash["context"] ) end - - private - - # Validate the type attribute. ActivityStreams specifies a set of core activity types. - # For simplicity, let's validate against a subset of them. - def validate_type - valid_types = %w[Create Update Delete Follow Like Add Remove Block Undo] - valid_types.include?(@type) - end - - # Validate the actor attribute. For this example, we'll just ensure it's present. - def validate_actor - !@actor.nil? && !@actor.empty? - end - - # Validate the object attribute. For this example, we'll ensure it's present. - def validate_object - !@object.nil? && !@object.empty? - end - - # Additional validations and methods based on scenarios can be added here. end end diff --git a/spec/activity_spec.rb b/spec/activity_spec.rb index ac0d23d..b426a2f 100644 --- a/spec/activity_spec.rb +++ b/spec/activity_spec.rb @@ -36,4 +36,109 @@ expect(activity.object).to eq("https://example.com/posts/1") end end + + describe "#initialize" do + context "when required fields are missing" do + it "raises an error if type is missing" do + expect do + ActivityPub::Activity.new(actor: "http://actor.example.com", object: "http://object.example.com") + end.to raise_error("Required field 'type' is missing") + end + + it "raises an error if actor is missing" do + expect do + ActivityPub::Activity.new(type: "Create", object: "http://object.example.com") + end.to raise_error("Required field 'actor' is missing") + end + + it "raises an error if object is missing" do + expect do + ActivityPub::Activity.new(type: "Create", actor: "http://actor.example.com") + end.to raise_error("Required field 'object' is missing") + end + end + + context "when an unknown type is provided" do + it "raises an error" do + expect do + ActivityPub::Activity.new(type: "UnknownType", actor: "http://actor.example.com", object: "http://object.example.com") + end.to raise_error("'UnknownType' is not a recognized activity type") + end + end + + context "when an invalid URL format is provided" do + it "raises an error for invalid actor URL" do + expect do + ActivityPub::Activity.new(type: "Create", actor: "invalid_actor", object: "http://object.example.com") + end.to raise_error("Invalid URL format: 'invalid_actor'") + end + + it "raises an error for invalid object URL" do + expect do + ActivityPub::Activity.new(type: "Create", actor: "http://actor.example.com", object: "invalid_object") + end.to raise_error("Invalid URL format: 'invalid_object'") + end + end + + context "when an invalid timestamp is provided" do + it "raises an error" do + expect do + ActivityPub::Activity.new(type: "Create", actor: "http://actor.example.com", object: "http://object.example.com", published: "invalid_timestamp") + end.to raise_error("Invalid timestamp format: 'invalid_timestamp'") + end + end + end + + describe "#to_h" do + let(:activity) do + ActivityPub::Activity.new( + type: "Create", + actor: "http://actor.example.com", + object: "http://object.example.com", + target: "http://target.example.com", + to: ["http://recipient1.example.com", "http://recipient2.example.com"], + cc: ["http://cc1.example.com"] + ) + end + + it "returns a hash representation of the activity" do + expect(activity.to_h).to include({ + id: nil, + type: "Create", + actor: "http://actor.example.com", + object: "http://object.example.com", + target: "http://target.example.com", + # published: a_kind_of(String), # As this is generated, we just check the type + to: ["http://recipient1.example.com", "http://recipient2.example.com"], + cc: ["http://cc1.example.com"], + bcc: nil, + context: nil + }) + + expect(activity.to_h[:published]).to match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/) + end + end + + describe ".from_h" do + let(:hash_representation) do + { + "type" => "Create", + "actor" => "http://actor.example.com", + "object" => "http://object.example.com", + "target" => "http://target.example.com", + "to" => ["http://recipient1.example.com", "http://recipient2.example.com"], + "cc" => ["http://cc1.example.com"] + } + end + + it "returns an activity instance from a hash representation" do + activity = ActivityPub::Activity.from_h(hash_representation) + expect(activity.type).to eq("Create") + expect(activity.actor).to eq("http://actor.example.com") + expect(activity.object).to eq("http://object.example.com") + expect(activity.target).to eq("http://target.example.com") + expect(activity.to).to eq(["http://recipient1.example.com", "http://recipient2.example.com"]) + expect(activity.cc).to eq(["http://cc1.example.com"]) + end + end end