From 5cf37248cc3f5d981bf42c911223cb281d3e4fde Mon Sep 17 00:00:00 2001
From: Nik Clayton <nik@ngo.org.uk>
Date: Sat, 14 Dec 2024 16:50:41 +0100
Subject: [PATCH] Ensure datetime/date are serialized to correct format
 (#33086)

---
 spec/fabricators/status_edit_fabricator.rb    |  5 +++
 ...ionship_severance_event_serializer_spec.rb |  8 +++-
 .../field_serializer_spec.rb                  | 19 +++++++++
 .../rest/account_serializer_spec.rb           | 21 ++++++++++
 .../rest/account_warning_serializer_spec.rb   |  1 +
 .../rest/admin/account_serializer_spec.rb     |  8 ++++
 .../rest/admin/cohort_serializer_spec.rb      |  2 +
 .../admin/domain_allow_serializer_spec.rb     | 15 +++++++
 .../admin/domain_block_serializer_spec.rb     | 15 +++++++
 .../email_domain_block_serializer_spec.rb     | 15 +++++++
 .../rest/admin/ip_block_serializer_spec.rb    | 23 ++++++++++
 .../rest/admin/ip_serializer_spec.rb          | 15 +++++++
 .../rest/admin/measure_serializer_spec.rb     | 18 ++++++++
 .../rest/admin/report_serializer_spec.rb      | 27 ++++++++++++
 .../rest/announcement_serializer_spec.rb      | 28 +++++++++++++
 .../extended_description_serializer_spec.rb   | 10 +++--
 .../rest/featured_tag_serializer_spec.rb      | 24 +++++++++++
 .../rest/filter_serializer_spec.rb            | 20 +++++++++
 .../rest/marker_serializer_spec.rb            | 20 +++++++++
 .../rest/muted_account_serializer_spec.rb     | 42 +++++++++++++++++++
 .../notification_group_serializer_spec.rb     | 20 +++++++++
 .../notification_request_serializer_spec.rb   | 31 ++++++++++++++
 .../rest/notification_serializer_spec.rb      | 20 +++++++++
 spec/serializers/rest/poll_serializer_spec.rb | 25 +++++++++++
 .../rest/report_serializer_spec.rb            | 33 +++++++++++++++
 .../rest/scheduled_status_serializer_spec.rb  | 21 ++++++++++
 .../rest/status_edit_serializer_spec.rb       | 20 +++++++++
 .../rest/status_serializer_spec.rb            | 14 +++++++
 28 files changed, 515 insertions(+), 5 deletions(-)
 create mode 100644 spec/fabricators/status_edit_fabricator.rb
 create mode 100644 spec/serializers/rest/account_serializer/field_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/domain_allow_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/domain_block_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/email_domain_block_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/ip_block_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/ip_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/measure_serializer_spec.rb
 create mode 100644 spec/serializers/rest/admin/report_serializer_spec.rb
 create mode 100644 spec/serializers/rest/announcement_serializer_spec.rb
 create mode 100644 spec/serializers/rest/featured_tag_serializer_spec.rb
 create mode 100644 spec/serializers/rest/filter_serializer_spec.rb
 create mode 100644 spec/serializers/rest/marker_serializer_spec.rb
 create mode 100644 spec/serializers/rest/muted_account_serializer_spec.rb
 create mode 100644 spec/serializers/rest/notification_group_serializer_spec.rb
 create mode 100644 spec/serializers/rest/notification_request_serializer_spec.rb
 create mode 100644 spec/serializers/rest/notification_serializer_spec.rb
 create mode 100644 spec/serializers/rest/poll_serializer_spec.rb
 create mode 100644 spec/serializers/rest/report_serializer_spec.rb
 create mode 100644 spec/serializers/rest/scheduled_status_serializer_spec.rb
 create mode 100644 spec/serializers/rest/status_edit_serializer_spec.rb

diff --git a/spec/fabricators/status_edit_fabricator.rb b/spec/fabricators/status_edit_fabricator.rb
new file mode 100644
index 000000000..4df61dcfd
--- /dev/null
+++ b/spec/fabricators/status_edit_fabricator.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+Fabricator(:status_edit) do
+  status { Fabricate.build(:status) }
+end
diff --git a/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb b/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb
index 95b4d7dcd..a69e1c277 100644
--- a/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb
+++ b/spec/serializers/rest/account_relationship_severance_event_serializer_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
 RSpec.describe REST::AccountRelationshipSeveranceEventSerializer do
   subject { serialized_record_json(record, described_class) }
 
-  let(:record) { Fabricate.build :account_relationship_severance_event, id: 123 }
+  let(:record) { Fabricate.build :account_relationship_severance_event, id: 123, created_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
 
   describe 'serialization' do
     it 'returns expected values' do
@@ -15,4 +15,10 @@ RSpec.describe REST::AccountRelationshipSeveranceEventSerializer do
         )
     end
   end
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
 end
diff --git a/spec/serializers/rest/account_serializer/field_serializer_spec.rb b/spec/serializers/rest/account_serializer/field_serializer_spec.rb
new file mode 100644
index 000000000..57f2b529c
--- /dev/null
+++ b/spec/serializers/rest/account_serializer/field_serializer_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::AccountSerializer::FieldSerializer do
+  subject { serialized_record_json(field, described_class) }
+
+  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
+  let(:user)    { Fabricate(:user) }
+  let(:account) { user.account }
+
+  context 'when verified_at is populated' do
+    let(:field) { Account::Field.new(account, 'name' => 'Foo', 'value' => 'Bar', 'verified_at' => default_datetime) }
+
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['verified_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb
index 7daa0796a..37c6a776b 100644
--- a/spec/serializers/rest/account_serializer_spec.rb
+++ b/spec/serializers/rest/account_serializer_spec.rb
@@ -5,6 +5,7 @@ require 'rails_helper'
 RSpec.describe REST::AccountSerializer do
   subject { serialized_record_json(account, described_class) }
 
+  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
   let(:role)    { Fabricate(:user_role, name: 'Role', highlighted: true) }
   let(:user)    { Fabricate(:user, role: role) }
   let(:account) { user.account }
@@ -44,4 +45,24 @@ RSpec.describe REST::AccountSerializer do
       expect(subject['memorial']).to be true
     end
   end
+
+  context 'when created_at is populated' do
+    before do
+      account.account_stat.update!(created_at: default_datetime)
+    end
+
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
+  context 'when last_status_at is populated' do
+    before do
+      account.account_stat.update!(last_status_at: default_datetime)
+    end
+
+    it 'is serialized as yyyy-mm-dd' do
+      expect(subject['last_status_at']).to eq('2024-11-28')
+    end
+  end
 end
diff --git a/spec/serializers/rest/account_warning_serializer_spec.rb b/spec/serializers/rest/account_warning_serializer_spec.rb
index 0f335d121..ea2e48fbb 100644
--- a/spec/serializers/rest/account_warning_serializer_spec.rb
+++ b/spec/serializers/rest/account_warning_serializer_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe REST::AccountWarningSerializer do
           'id' => be_a(String).and(eq('123')),
           'status_ids' => be_a(Array).and(eq(['456', '789']))
         )
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
     end
   end
 end
diff --git a/spec/serializers/rest/admin/account_serializer_spec.rb b/spec/serializers/rest/admin/account_serializer_spec.rb
index 58f58a997..ac7cec0ba 100644
--- a/spec/serializers/rest/admin/account_serializer_spec.rb
+++ b/spec/serializers/rest/admin/account_serializer_spec.rb
@@ -5,6 +5,14 @@ require 'rails_helper'
 RSpec.describe REST::Admin::AccountSerializer do
   subject { serialized_record_json(record, described_class) }
 
+  context 'when created_at is populated' do
+    let(:record) { Fabricate :account, user: Fabricate(:user) }
+
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
   describe 'created_by_application_id' do
     context 'when account is application-created' do
       let(:record) { Fabricate :account, user: Fabricate(:user, created_by_application: application) }
diff --git a/spec/serializers/rest/admin/cohort_serializer_spec.rb b/spec/serializers/rest/admin/cohort_serializer_spec.rb
index ed6067c0d..a2bd8fbfa 100644
--- a/spec/serializers/rest/admin/cohort_serializer_spec.rb
+++ b/spec/serializers/rest/admin/cohort_serializer_spec.rb
@@ -14,6 +14,8 @@ RSpec.describe REST::Admin::CohortSerializer do
           'data' => be_a(Array),
           'period' => /2024-01-01/
         )
+      expect { DateTime.rfc3339(subject['period']) }.to_not raise_error
+      subject['data'].each { |datum| expect { DateTime.rfc3339(datum['date']) }.to_not raise_error }
     end
   end
 end
diff --git a/spec/serializers/rest/admin/domain_allow_serializer_spec.rb b/spec/serializers/rest/admin/domain_allow_serializer_spec.rb
new file mode 100644
index 000000000..39c013d31
--- /dev/null
+++ b/spec/serializers/rest/admin/domain_allow_serializer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::DomainAllowSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:record) { Fabricate(:domain_allow) }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/domain_block_serializer_spec.rb b/spec/serializers/rest/admin/domain_block_serializer_spec.rb
new file mode 100644
index 000000000..5ab3ddc16
--- /dev/null
+++ b/spec/serializers/rest/admin/domain_block_serializer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::DomainBlockSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:record) { Fabricate(:domain_block) }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb b/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb
new file mode 100644
index 000000000..2a1501a15
--- /dev/null
+++ b/spec/serializers/rest/admin/email_domain_block_serializer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::EmailDomainBlockSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:record) { Fabricate(:email_domain_block) }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/ip_block_serializer_spec.rb b/spec/serializers/rest/admin/ip_block_serializer_spec.rb
new file mode 100644
index 000000000..c1239b505
--- /dev/null
+++ b/spec/serializers/rest/admin/ip_block_serializer_spec.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::IpBlockSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:record) { Fabricate(:ip_block) }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
+  context 'when expires_at is populated' do
+    let(:record) { Fabricate(:ip_block, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0)) }
+
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/ip_serializer_spec.rb b/spec/serializers/rest/admin/ip_serializer_spec.rb
new file mode 100644
index 000000000..f93eada75
--- /dev/null
+++ b/spec/serializers/rest/admin/ip_serializer_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::IpSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:record) { UserIp.new(used_at: 3.days.ago) }
+
+  context 'when used_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['used_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/measure_serializer_spec.rb b/spec/serializers/rest/admin/measure_serializer_spec.rb
new file mode 100644
index 000000000..08c7170a4
--- /dev/null
+++ b/spec/serializers/rest/admin/measure_serializer_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::MeasureSerializer do
+  subject { serialized_record_json(record, described_class) }
+
+  let(:start_at) { 2.days.ago }
+  let(:end_at) { Time.now.utc }
+  let(:params) { ActionController::Parameters.new({ instance_accounts: [123] }) }
+  let(:record) { Admin::Metrics::Measure::ActiveUsersMeasure.new(start_at, end_at, params) }
+
+  context 'when start_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      subject['data'].each { |datum| expect { DateTime.rfc3339(datum['date']) }.to_not raise_error }
+    end
+  end
+end
diff --git a/spec/serializers/rest/admin/report_serializer_spec.rb b/spec/serializers/rest/admin/report_serializer_spec.rb
new file mode 100644
index 000000000..c0f841d6b
--- /dev/null
+++ b/spec/serializers/rest/admin/report_serializer_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::Admin::ReportSerializer do
+  subject { serialized_record_json(report, described_class) }
+
+  let(:report) { Fabricate(:report) }
+
+  context 'with created_at' do
+    it 'is serialized as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
+  context 'with action_taken_at' do
+    let(:acting_account) { Fabricate(:account) }
+
+    before do
+      report.resolve!(acting_account)
+    end
+
+    it 'is serialized as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['action_taken_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/announcement_serializer_spec.rb b/spec/serializers/rest/announcement_serializer_spec.rb
new file mode 100644
index 000000000..6b746e5c2
--- /dev/null
+++ b/spec/serializers/rest/announcement_serializer_spec.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::AnnouncementSerializer do
+  subject do
+    serialized_record_json(
+      announcement,
+      described_class,
+      options: {
+        scope: current_user,
+        scope_name: :current_user,
+      }
+    )
+  end
+
+  let(:current_user) { Fabricate(:user) }
+  let(:announcement) { Fabricate(:announcement, starts_at: 10.days.ago, published_at: 10.days.ago, ends_at: 5.days.from_now) }
+
+  context 'when date fields are populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['starts_at']) }.to_not raise_error
+      expect { DateTime.rfc3339(subject['ends_at']) }.to_not raise_error
+      expect { DateTime.rfc3339(subject['published_at']) }.to_not raise_error
+      expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/extended_description_serializer_spec.rb b/spec/serializers/rest/extended_description_serializer_spec.rb
index d7bd078e1..dc6e86e90 100644
--- a/spec/serializers/rest/extended_description_serializer_spec.rb
+++ b/spec/serializers/rest/extended_description_serializer_spec.rb
@@ -5,9 +5,11 @@ require 'rails_helper'
 RSpec.describe REST::ExtendedDescriptionSerializer do
   subject { serialized_record_json(record, described_class) }
 
+  let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
+
   describe 'serialization' do
     context 'with text present' do
-      let(:record) { ExtendedDescription.new text: 'Hello world', updated_at: Date.new(2024, 1, 1) }
+      let(:record) { ExtendedDescription.new text: 'Hello world', updated_at: default_datetime }
 
       it 'returns expected values' do
         expect(subject)
@@ -15,19 +17,19 @@ RSpec.describe REST::ExtendedDescriptionSerializer do
             'content' => eq(<<~HTML),
               <p>Hello world</p>
             HTML
-            'updated_at' => eq('2024-01-01')
+            'updated_at' => eq('2024-11-28T16:20:00+00:00')
           )
       end
     end
 
     context 'with text missing' do
-      let(:record) { ExtendedDescription.new text: nil, updated_at: Date.new(2024, 1, 1) }
+      let(:record) { ExtendedDescription.new text: nil, updated_at: default_datetime }
 
       it 'returns expected values' do
         expect(subject)
           .to include(
             'content' => eq(''),
-            'updated_at' => eq('2024-01-01')
+            'updated_at' => eq('2024-11-28T16:20:00+00:00')
           )
       end
     end
diff --git a/spec/serializers/rest/featured_tag_serializer_spec.rb b/spec/serializers/rest/featured_tag_serializer_spec.rb
new file mode 100644
index 000000000..883fce1aa
--- /dev/null
+++ b/spec/serializers/rest/featured_tag_serializer_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::FeaturedTagSerializer do
+  subject do
+    serialized_record_json(
+      featured_tag,
+      described_class
+    )
+  end
+
+  let(:featured_tag) { Fabricate :featured_tag }
+
+  context 'when last_status_at is populated' do
+    before do
+      featured_tag.increment(DateTime.new(2024, 11, 28, 16, 20, 0))
+    end
+
+    it 'is serialized as yyyy-mm-dd' do
+      expect(subject['last_status_at']).to eq('2024-11-28')
+    end
+  end
+end
diff --git a/spec/serializers/rest/filter_serializer_spec.rb b/spec/serializers/rest/filter_serializer_spec.rb
new file mode 100644
index 000000000..c2c0e1635
--- /dev/null
+++ b/spec/serializers/rest/filter_serializer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::FilterSerializer do
+  subject do
+    serialized_record_json(
+      filter,
+      described_class
+    )
+  end
+
+  let(:filter) { Fabricate :custom_filter, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
+
+  context 'when expires_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/marker_serializer_spec.rb b/spec/serializers/rest/marker_serializer_spec.rb
new file mode 100644
index 000000000..8b8285c9e
--- /dev/null
+++ b/spec/serializers/rest/marker_serializer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::MarkerSerializer do
+  subject do
+    serialized_record_json(
+      marker,
+      described_class
+    )
+  end
+
+  let(:marker) { Fabricate :marker }
+
+  context 'when updated_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/muted_account_serializer_spec.rb b/spec/serializers/rest/muted_account_serializer_spec.rb
new file mode 100644
index 000000000..2a6dd9fe1
--- /dev/null
+++ b/spec/serializers/rest/muted_account_serializer_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::MutedAccountSerializer do
+  subject do
+    serialized_record_json(
+      mutee,
+      described_class,
+      options: {
+        scope: current_user,
+        scope_name: :current_user,
+      }
+    )
+  end
+
+  let(:current_user) { Fabricate(:user) }
+  let(:account) { current_user.account }
+  let(:mutee) { Fabricate(:account, username: 'mutee') }
+
+  context 'when mute_expires_at is populated and non-nil' do
+    before do
+      account.follow!(mutee)
+      account.mute!(mutee, duration: 1.day)
+    end
+
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['mute_expires_at']) }.to_not raise_error
+    end
+  end
+
+  context 'when mute has no duration' do
+    before do
+      account.follow!(mutee)
+      account.mute!(mutee)
+    end
+
+    it 'has a nil mute_expires_at' do
+      expect(subject['mute_expires_at']).to be_nil
+    end
+  end
+end
diff --git a/spec/serializers/rest/notification_group_serializer_spec.rb b/spec/serializers/rest/notification_group_serializer_spec.rb
new file mode 100644
index 000000000..2a1293292
--- /dev/null
+++ b/spec/serializers/rest/notification_group_serializer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::NotificationGroupSerializer do
+  subject do
+    serialized_record_json(
+      notification_group,
+      described_class
+    )
+  end
+
+  let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
+
+  context 'when latest_page_notification_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['latest_page_notification_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/notification_request_serializer_spec.rb b/spec/serializers/rest/notification_request_serializer_spec.rb
new file mode 100644
index 000000000..34332679d
--- /dev/null
+++ b/spec/serializers/rest/notification_request_serializer_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::NotificationRequestSerializer do
+  subject do
+    serialized_record_json(
+      notification_request,
+      described_class,
+      options: {
+        scope: current_user,
+        scope_name: :current_user,
+      }
+    )
+  end
+
+  let(:current_user) { Fabricate(:user) }
+  let(:notification_request) { Fabricate :notification_request }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
+  context 'when updated_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/notification_serializer_spec.rb b/spec/serializers/rest/notification_serializer_spec.rb
new file mode 100644
index 000000000..7296c1b93
--- /dev/null
+++ b/spec/serializers/rest/notification_serializer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::NotificationSerializer do
+  subject do
+    serialized_record_json(
+      notification,
+      described_class
+    )
+  end
+
+  let(:notification) { Fabricate :notification }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/poll_serializer_spec.rb b/spec/serializers/rest/poll_serializer_spec.rb
new file mode 100644
index 000000000..71ed9deb3
--- /dev/null
+++ b/spec/serializers/rest/poll_serializer_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::PollSerializer do
+  subject do
+    serialized_record_json(
+      poll,
+      described_class,
+      options: {
+        scope: current_user,
+        scope_name: :current_user,
+      }
+    )
+  end
+
+  let(:current_user) { Fabricate(:user) }
+  let(:poll) { Fabricate :poll }
+
+  context 'when expires_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/report_serializer_spec.rb b/spec/serializers/rest/report_serializer_spec.rb
new file mode 100644
index 000000000..5cbeb0fee
--- /dev/null
+++ b/spec/serializers/rest/report_serializer_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::ReportSerializer do
+  subject do
+    serialized_record_json(
+      report,
+      described_class
+    )
+  end
+
+  let(:status) { Fabricate(:status) }
+  let(:report) { Fabricate(:report, status_ids: [status.id]) }
+
+  context 'with created_at' do
+    it 'is serialized as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+
+  context 'with action_taken_at' do
+    let(:acting_account) { Fabricate(:account) }
+
+    before do
+      report.resolve!(acting_account)
+    end
+
+    it 'is serialized as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['action_taken_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/scheduled_status_serializer_spec.rb b/spec/serializers/rest/scheduled_status_serializer_spec.rb
new file mode 100644
index 000000000..7d0d7c96b
--- /dev/null
+++ b/spec/serializers/rest/scheduled_status_serializer_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::ScheduledStatusSerializer do
+  subject do
+    serialized_record_json(
+      scheduled_status,
+      described_class
+    )
+  end
+
+  let(:account) { Fabricate(:account) }
+  let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, account: account) }
+
+  context 'with scheduled_at' do
+    it 'is serialized as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['scheduled_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/status_edit_serializer_spec.rb b/spec/serializers/rest/status_edit_serializer_spec.rb
new file mode 100644
index 000000000..1f5ac54b7
--- /dev/null
+++ b/spec/serializers/rest/status_edit_serializer_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe REST::StatusEditSerializer do
+  subject do
+    serialized_record_json(
+      status_edit,
+      described_class
+    )
+  end
+
+  let(:status_edit) { Fabricate(:status_edit) }
+
+  context 'when created_at is populated' do
+    it 'parses as RFC 3339 datetime' do
+      expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+    end
+  end
+end
diff --git a/spec/serializers/rest/status_serializer_spec.rb b/spec/serializers/rest/status_serializer_spec.rb
index e96d1fbe6..ca0591eb6 100644
--- a/spec/serializers/rest/status_serializer_spec.rb
+++ b/spec/serializers/rest/status_serializer_spec.rb
@@ -51,5 +51,19 @@ RSpec.describe REST::StatusSerializer do
         expect(subject['favourites_count']).to eq(40)
       end
     end
+
+    context 'with created_at' do
+      it 'is serialized as RFC 3339 datetime' do
+        expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
+      end
+    end
+
+    context 'when edited_at is populated' do
+      let(:status) { Fabricate.build :status, edited_at: 3.days.ago }
+
+      it 'is serialized as RFC 3339 datetime' do
+        expect { DateTime.rfc3339(subject['edited_at']) }.to_not raise_error
+      end
+    end
   end
 end