otwarchive-symphonyarchive/spec/controllers/api/v2/api_works_spec.rb
2026-03-11 22:22:11 +00:00

552 lines
20 KiB
Ruby

# frozen_string_literal: true
require "spec_helper"
require "controllers/api/api_helper"
include ApiHelper
describe "API v2 WorksController - Create works", type: :request do
let(:archivist) { create(:archivist) }
describe "API import with a valid archivist" do
before :each do
ApiHelper.mock_external
end
after :each do
WebMock.reset!
end
it "returns 200 OK when all stories are created" do
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
assert_equal 200, response.status
end
it "returns 400 error with an error message when no stories are created" do
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://bar"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
assert_equal 400, response.status
end
it "returns 400 OK with an error message when only some stories are created" do
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://foo"] },
{ external_author_name: "bar2",
external_author_email: "bar2@foo.com",
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
assert_equal 400, response.status
end
it "returns the original id" do
valid_params = {
archivist: archivist.login,
works: [
{ id: "123",
external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
expect(parsed_body[:works].first[:original_id]).to eq("123")
end
it "returns human-readable messages as an array" do
valid_params = {
archivist: archivist.login,
works: [
{ id: "123",
external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
expect(parsed_body[:works].first[:messages]).to be_a(Array)
end
it "sends claim emails if send_claim_email is true" do
valid_params = {
archivist: archivist.login,
send_claim_emails: 1,
works: [
{ id: "123",
external_author_name: "bar",
external_author_email: "send_invite@ao3.org",
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
expect(parsed_body[:messages]).to include("Claim emails sent to bar.")
end
it "returns 400 Bad Request if no works are specified" do
valid_params = {
archivist: archivist.login
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
assert_equal 400, response.status
end
it "returns a helpful message if the external work contains no text" do
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: "bar",
external_author_email: "bar@foo.com",
chapter_urls: ["http://no-content"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
expect(parsed_body[:works].first[:messages].first).to start_with("Unable to import this work.")
end
describe "Provided API metadata should be used if present" do
before do
Language.create(short: "es", name: "Español")
mock_external
archivist = create(:archivist)
valid_params = {
archivist: archivist.login,
works: [
{ id: "123",
title: api_fields[:title],
summary: api_fields[:summary],
fandoms: api_fields[:fandoms],
warnings: api_fields[:warnings],
characters: api_fields[:characters],
rating: api_fields[:rating],
relationships: api_fields[:relationships],
categories: api_fields[:categories],
additional_tags: api_fields[:freeform],
language_code: api_fields[:language_code],
external_author_name: api_fields[:external_author_name],
external_author_email: api_fields[:external_author_email],
notes: api_fields[:notes],
chapter_urls: ["http://foo"]
}
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
@work = Work.find_by_url(parsed_body[:works].first[:original_url])
end
after do
WebMock.reset!
end
it "API should override content for Title" do
expect(@work.title).to eq(api_fields[:title])
end
it "API should override content for Summary" do
expect(@work.summary).to eq("<p>" + api_fields[:summary] + "</p>")
end
it "API should override content for Fandoms" do
expect(@work.fandoms.first.name).to eq(api_fields[:fandoms])
end
it "API should override content for Warnings" do
expect(@work.archive_warnings.first.name).to eq(api_fields[:warnings])
end
it "API should override content for Characters" do
expect(@work.characters.flat_map(&:name)).to eq(api_fields[:characters].split(", "))
end
it "API should override content for Ratings" do
expect(@work.ratings.first.name).to eq(api_fields[:rating])
end
it "API should override content for Relationships" do
expect(@work.relationships.first.name).to eq(api_fields[:relationships])
end
it "API should override content for Categories" do
expect(@work.categories.first.name).to eq(api_fields[:categories])
end
it "API should override content for Additional Tags" do
expect(@work.freeforms.flat_map(&:name)).to eq(api_fields[:freeform].split(", "))
end
it "API should override content for Language" do
expect(Language.find_by(id: @work.language_id).short).to eq(api_fields[:language_code])
end
it "API should override content for Notes" do
expect(@work.notes).to eq("<p>" + api_fields[:notes] + "</p>")
end
it "API should override content for Author pseud" do
expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
end
end
describe "Metadata should be extracted from content if no API metadata is supplied" do
before do
mock_external
archivist = create(:archivist)
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: api_fields[:external_author_name],
external_author_email: api_fields[:external_author_email],
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
@work = Work.find_by_url(parsed_body[:works].first[:original_url])
created_user = ExternalAuthor.find_by(email: api_fields[:external_author_email])
created_user&.destroy
end
after do
WebMock.reset!
end
it "Title should be detected from the content" do
expect(@work.title).to eq(content_fields[:title])
end
it "Summary should be detected from the content" do
expect(@work.summary).to eq("<p>" + content_fields[:summary] + "</p>")
end
it "Date should be detected from the content" do
expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
end
it "Chapter title should be detected from the content" do
expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
end
it "Fandoms should be detected from the content" do
expect(@work.fandoms.first.name).to eq(content_fields[:fandoms])
end
it "Warnings should be detected from the content" do
expect(@work.archive_warnings.first.name).to eq(content_fields[:warnings])
end
it "Characters should be detected from the content" do
expect(@work.characters.flat_map(&:name)).to eq(content_fields[:characters].split(", "))
end
it "Ratings should be detected from the content" do
expect(@work.ratings.first.name).to eq(content_fields[:rating])
end
it "Relationships should be detected from the content" do
expect(@work.relationships.first.name).to eq(content_fields[:relationships])
end
it "Categories should be detected from the content" do
expect(@work.categories).to be_empty
end
it "Additional Tags should be detected from the content" do
expect(@work.freeforms.flat_map(&:name)).to eq(content_fields[:freeform].split(", "))
end
it "Notes should be detected from the content" do
expect(@work.notes).to eq("<p>" + content_fields[:notes] + "</p>")
end
end
describe "Imports should use fallback values or nil if no metadata is supplied" do
before do
mock_external
archivist = create(:archivist)
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: api_fields[:external_author_name],
external_author_email: api_fields[:external_author_email],
chapter_urls: ["http://no-metadata"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
@work = Work.find_by_url(parsed_body[:works].first[:original_url])
end
after do
WebMock.reset!
end
it "Title should be the default fallback title for imported works" do
expect(@work.title).to eq("Untitled Imported Work")
end
it "Summary should be blank" do
expect(@work.summary).to eq("")
end
it "Date should be todayish" do
expect(@work.created_at.utc.to_date).to eq(Time.now.getutc.to_date)
end
it "Chapter title should be blank" do
expect(@work.chapters.first.title).to be_nil
end
it "Fandoms should be the default Archive fandom ('No Fandom')" do
expect(@work.fandoms.first.name).to eq(ArchiveConfig.FANDOM_NO_TAG_NAME)
end
it "Warnings should be the default Archive warning" do
expect(@work.archive_warnings.first.name).to eq(ArchiveConfig.WARNING_DEFAULT_TAG_NAME)
end
it "Characters should be empty" do
expect(@work.characters).to be_empty
end
it "Ratings should be the default Archive rating" do
expect(@work.ratings.first.name).to eq(ArchiveConfig.RATING_DEFAULT_TAG_NAME)
end
it "Relationships should be empty" do
expect(@work.relationships).to be_empty
end
it "Categories should be empty" do
expect(@work.categories).to be_empty
end
it "Additional Tags should be empty" do
expect(@work.freeforms).to be_empty
end
it "Language should be English" do
expect(@work.language_id).to eq(Language.find_by(short: "en").id)
end
it "Notes should be empty" do
expect(@work.notes).to be_empty
end
it "Author pseud" do
expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
end
end
describe "Provided API metadata should be used if present and tag detection is turned off" do
before do
mock_external
archivist = create(:archivist)
valid_params = {
archivist: archivist.login,
works: [
{ id: "123",
title: api_fields[:title],
detect_tags: false,
summary: api_fields[:summary],
fandoms: api_fields[:fandoms],
warnings: api_fields[:warnings],
characters: api_fields[:characters],
rating: api_fields[:rating],
relationships: api_fields[:relationships],
categories: api_fields[:categories],
additional_tags: api_fields[:freeform],
external_author_name: api_fields[:external_author_name],
external_author_email: api_fields[:external_author_email],
notes: api_fields[:notes],
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
@work = Work.find_by_url(parsed_body[:works].first[:original_url])
end
after do
WebMock.reset!
end
it "API should override content for Title" do
expect(@work.title).to eq(api_fields[:title])
end
it "API should override content for Summary" do
expect(@work.summary).to eq("<p>" + api_fields[:summary] + "</p>")
end
it "Date should be detected from the content" do
expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
end
it "Chapter title should be detected from the content" do
expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
end
it "API should override content for Fandoms" do
expect(@work.fandoms.first.name).to eq(api_fields[:fandoms])
end
it "API should override content for Warnings" do
expect(@work.archive_warnings.first.name).to eq(api_fields[:warnings])
end
it "API should override content for Characters" do
expect(@work.characters.flat_map(&:name)).to eq(api_fields[:characters].split(", "))
end
it "API should override content for Ratings" do
expect(@work.ratings.first.name).to eq(api_fields[:rating])
end
it "API should override content for Relationships" do
expect(@work.relationships.first.name).to eq(api_fields[:relationships])
end
it "API should override content for Categories" do
expect(@work.categories.first.name).to eq(api_fields[:categories])
end
it "API should override content for Additional Tags" do
expect(@work.freeforms.flat_map(&:name)).to eq(api_fields[:freeform].split(", "))
end
it "API should override content for Notes" do
expect(@work.notes).to eq("<p>" + api_fields[:notes] + "</p>")
end
it "API should override content for Author pseud" do
expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
end
end
describe "Some fields should be detected and others use fallback values or nil if no metadata is supplied and tag detection is turned off" do
before do
mock_external
archivist = create(:archivist)
valid_params = {
archivist: archivist.login,
works: [
{ external_author_name: api_fields[:external_author_name],
external_author_email: api_fields[:external_author_email],
detect_tags: false,
chapter_urls: ["http://foo"] }
]
}
post "/api/v2/works", params: valid_params.to_json, headers: valid_headers
parsed_body = JSON.parse(response.body, symbolize_names: true)
@work = Work.find_by_url(parsed_body[:works].first[:original_url])
end
after do
WebMock.reset!
end
it "Title should be detected from the content" do
expect(@work.title).to eq(content_fields[:title])
end
it "Summary should be detected from the content" do
expect(@work.summary).to eq("<p>" + content_fields[:summary] + "</p>")
end
it "Date should be detected from the content" do
expect(@work.revised_at.to_date).to eq(content_fields[:date].to_date)
end
it "Chapter title should be detected from the content" do
expect(@work.chapters.first.title).to eq(content_fields[:chapter_title])
end
it "Fandoms should be the default Archive fandom ('No Fandom')" do
expect(@work.fandoms.first.name).to eq(ArchiveConfig.FANDOM_NO_TAG_NAME)
end
it "Warnings should be the default Archive warning" do
expect(@work.archive_warnings.first.name).to eq(ArchiveConfig.WARNING_DEFAULT_TAG_NAME)
end
it "Characters should be empty" do
expect(@work.characters).to be_empty
end
it "Ratings should be the default Archive rating" do
expect(@work.ratings.first.name).to eq(ArchiveConfig.RATING_DEFAULT_TAG_NAME)
end
it "Relationships should be empty" do
expect(@work.relationships).to be_empty
end
it "Categories should be empty" do
expect(@work.categories).to be_empty
end
it "Additional Tags should be empty" do
expect(@work.freeforms).to be_empty
end
it "Notes should be empty" do
expect(@work.notes).to be_empty
end
it "Author pseud" do
expect(@work.external_author_names.first.name).to eq(api_fields[:external_author_name])
end
end
end
end
describe "API v2 WorksController - Unit Tests", type: :request do
before do
@under_test = Api::V2::WorksController.new
end
it "work_url_from_external returns an error message when the work URL is blank" do
work_url_response = @under_test.instance_eval { find_work_by_import_url("") }
expect(work_url_response[:message]).to eq "Please provide the original URL for the work."
end
it "work_url_from_external returns the work url when a work is found" do
work1 = create(:work, imported_from_url: "http://foo")
work_url_response = @under_test.instance_eval { find_work_by_import_url("http://foo") }
expect(work_url_response[:works].first).to eq work1
end
it "notify_and_return_authors calls find_or_invite on each external author" do
user = create(:user)
author1 = create(:external_author)
author2 = create(:external_author)
work = create(:work)
name1 = create(:external_author_name, name: 'n1', external_author: author1)
name2 = create(:external_author_name, name: 'n2', external_author: author2)
create(:external_creatorship, external_author_name: name1, creation: work)
create(:external_creatorship, external_author_name: name2, creation: work)
@under_test.instance_eval { notify_and_return_authors([work], user) }
emails = Invitation.all.map(&:invitee_email)
expect(emails).to include(author1.email)
expect(emails).to include(author2.email)
end
describe "work_errors" do
it "returns an error if a work doesn't contain chapter urls" do
work = { chapter_urls: [] }
error_message = @under_test.instance_eval { work_errors(work) }
expect(error_message[1][0]).to start_with "This work doesn't contain any chapter URLs."
end
it "returns an error if a work has too many chapters" do
loads_of_items = Array.new(210) { |_| "chapter_url" }
work = { chapter_urls: loads_of_items }
error_message = @under_test.instance_eval { work_errors(work) }
expect(error_message[1][0]).to start_with "This work contains too many chapter URLs"
end
end
end