このコミットが含まれているのは:
@@ -52,16 +52,16 @@ RSpec.describe 'Gekanator games API', type: :request do
|
|||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns not found without an admin user' do
|
it 'returns unauthorized without a user' do
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
guessed_post_id: guessed_post.id,
|
guessed_post_id: guessed_post.id,
|
||||||
correct_post_id: guessed_post.id,
|
correct_post_id: guessed_post.id,
|
||||||
answers: [] }
|
answers: [] }
|
||||||
|
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns not found for a non-admin user' do
|
it 'stores a game for a non-admin user' do
|
||||||
sign_in_as user
|
sign_in_as user
|
||||||
|
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
@@ -69,7 +69,8 @@ RSpec.describe 'Gekanator games API', type: :request do
|
|||||||
correct_post_id: guessed_post.id,
|
correct_post_id: guessed_post.id,
|
||||||
answers: [{ question_id: 'tag:1', answer: 'yes' }] }
|
answers: [{ question_id: 'tag:1', answer: 'yes' }] }
|
||||||
|
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:created)
|
||||||
|
expect(GekanatorGame.find(json['id']).user).to eq(user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns not found for a non-admin user' do
|
it 'stores a game result for a non-admin user' do
|
||||||
sign_in_as member
|
sign_in_as member
|
||||||
|
|
||||||
post '/gekanator/games', params: {
|
post '/gekanator/games', params: {
|
||||||
@@ -138,7 +138,18 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
answers: []
|
answers: []
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:created)
|
||||||
|
expect(GekanatorGame.find(json['id']).user).to eq(member)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns unauthorized without a user' do
|
||||||
|
post '/gekanator/games', params: {
|
||||||
|
guessed_post_id: guessed_post.id,
|
||||||
|
correct_post_id: correct_post.id,
|
||||||
|
answers: []
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -261,17 +272,57 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns not found for a non-admin user' do
|
it 'allows a non-admin user to suggest a question for their own game' do
|
||||||
|
member_game = GekanatorGame.create!(
|
||||||
|
user: member,
|
||||||
|
guessed_post: guessed_post,
|
||||||
|
correct_post: correct_post,
|
||||||
|
won: false,
|
||||||
|
question_count: 1,
|
||||||
|
answers: [{ 'question_id' => 'tag:1', 'answer' => 'yes' }]
|
||||||
|
)
|
||||||
sign_in_as member
|
sign_in_as member
|
||||||
|
|
||||||
post '/gekanator/question_suggestions', params: {
|
expect {
|
||||||
gekanator_game_id: game.id,
|
post '/gekanator/question_suggestions', params: {
|
||||||
question_text: 'member question?',
|
gekanator_game_id: member_game.id,
|
||||||
answer: 'yes'
|
question_text: 'member question?',
|
||||||
}
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
}.to change { GekanatorQuestionSuggestion.count }.by(1)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:created)
|
||||||
|
expect(GekanatorQuestionSuggestion.last).to have_attributes(
|
||||||
|
gekanator_game_id: member_game.id,
|
||||||
|
user_id: member.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns not found for another user game' do
|
||||||
|
sign_in_as member
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post '/gekanator/question_suggestions', params: {
|
||||||
|
gekanator_game_id: game.id,
|
||||||
|
question_text: 'member question?',
|
||||||
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
}.not_to change { GekanatorQuestionSuggestion.count }
|
||||||
|
|
||||||
expect(response).to have_http_status(:not_found)
|
expect(response).to have_http_status(:not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns unauthorized without a user' do
|
||||||
|
expect {
|
||||||
|
post '/gekanator/question_suggestions', params: {
|
||||||
|
gekanator_game_id: game.id,
|
||||||
|
question_text: 'member question?',
|
||||||
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
}.not_to change { GekanatorQuestionSuggestion.count }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /gekanator/games/:id/extra_questions' do
|
describe 'GET /gekanator/games/:id/extra_questions' do
|
||||||
@@ -377,6 +428,38 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
expect(response).to have_http_status(:ok)
|
expect(response).to have_http_status(:ok)
|
||||||
expect(json['questions'].map { _1['id'] }).to contain_exactly(accepted.id)
|
expect(json['questions'].map { _1['id'] }).to contain_exactly(accepted.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows a non-admin user to fetch extra questions for their own game' do
|
||||||
|
member_game = GekanatorGame.create!(
|
||||||
|
user: member,
|
||||||
|
guessed_post: guessed_post,
|
||||||
|
correct_post: correct_post,
|
||||||
|
won: false,
|
||||||
|
question_count: 1,
|
||||||
|
answers: [{ 'question_id' => 'tag:1', 'answer' => 'yes' }]
|
||||||
|
)
|
||||||
|
accepted = create_post_similarity_question!(text: 'accepted?')
|
||||||
|
sign_in_as member
|
||||||
|
|
||||||
|
get "/gekanator/games/#{member_game.id}/extra_questions"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(json['questions'].map { _1['id'] }).to include(accepted.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns not found for another user game' do
|
||||||
|
sign_in_as member
|
||||||
|
|
||||||
|
get "/gekanator/games/#{game.id}/extra_questions"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns unauthorized without a user' do
|
||||||
|
get "/gekanator/games/#{game.id}/extra_questions"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST /gekanator/games/:id/extra_question_answers' do
|
describe 'POST /gekanator/games/:id/extra_question_answers' do
|
||||||
@@ -503,6 +586,69 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
|
|
||||||
expect(response).to have_http_status(:unprocessable_entity)
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'allows a non-admin user to answer extra questions for their own game' do
|
||||||
|
member_game = GekanatorGame.create!(
|
||||||
|
user: member,
|
||||||
|
guessed_post: guessed_post,
|
||||||
|
correct_post: correct_post,
|
||||||
|
won: false,
|
||||||
|
question_count: 1,
|
||||||
|
answers: [{ 'question_id' => 'tag:1', 'answer' => 'yes' }]
|
||||||
|
)
|
||||||
|
question = create_post_similarity_question!(text: 'extra?')
|
||||||
|
sign_in_as member
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/gekanator/games/#{member_game.id}/extra_question_answers", params: {
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
question_id: question.id,
|
||||||
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.to change { GekanatorQuestionExample.count }.by(1)
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:created)
|
||||||
|
expect(GekanatorQuestionExample.last).to have_attributes(
|
||||||
|
user_id: member.id,
|
||||||
|
gekanator_game_id: member_game.id
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns not found for another user game' do
|
||||||
|
question = create_post_similarity_question!(text: 'extra?')
|
||||||
|
sign_in_as member
|
||||||
|
|
||||||
|
expect {
|
||||||
|
post "/gekanator/games/#{game.id}/extra_question_answers", params: {
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
question_id: question.id,
|
||||||
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}.not_to change { GekanatorQuestionExample.count }
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns unauthorized without a user' do
|
||||||
|
question = create_post_similarity_question!(text: 'extra?')
|
||||||
|
|
||||||
|
post "/gekanator/games/#{game.id}/extra_question_answers", params: {
|
||||||
|
answers: [
|
||||||
|
{
|
||||||
|
question_id: question.id,
|
||||||
|
answer: 'yes'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET /gekanator/questions' do
|
describe 'GET /gekanator/questions' do
|
||||||
@@ -608,5 +754,33 @@ RSpec.describe 'Gekanator learning API', type: :request do
|
|||||||
'length' => 21
|
'length' => 21
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'returns title-contains questions without authentication' do
|
||||||
|
GekanatorQuestion.create!(
|
||||||
|
text: '題名に「結束バンド」が含まれる?',
|
||||||
|
kind: 'title',
|
||||||
|
source: 'ai_generated',
|
||||||
|
status: 'accepted',
|
||||||
|
priority_weight: 0.95,
|
||||||
|
condition: {
|
||||||
|
type: 'title-contains',
|
||||||
|
text: '結束バンド'
|
||||||
|
},
|
||||||
|
created_by: admin
|
||||||
|
)
|
||||||
|
|
||||||
|
get '/gekanator/questions'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
question_json = json['questions'].find { _1['id'] == 'title:contains:結束バンド' }
|
||||||
|
expect(question_json).to include(
|
||||||
|
'text' => '題名に「結束バンド」が含まれる?',
|
||||||
|
'kind' => 'title'
|
||||||
|
)
|
||||||
|
expect(question_json['condition']).to include(
|
||||||
|
'type' => 'title-contains',
|
||||||
|
'text' => '結束バンド'
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Gekanator::QuestionSuggestionAiConverter do
|
||||||
|
let(:user) { create(:user, :member) }
|
||||||
|
let(:guessed_post) { Post.create!(title: 'guess', url: 'https://example.com/guess') }
|
||||||
|
let(:correct_post) { Post.create!(title: 'correct', url: 'https://example.com/correct') }
|
||||||
|
let(:game) do
|
||||||
|
GekanatorGame.create!(
|
||||||
|
user: user,
|
||||||
|
guessed_post: guessed_post,
|
||||||
|
correct_post: correct_post,
|
||||||
|
won: false,
|
||||||
|
question_count: 1,
|
||||||
|
answers: [{ 'question_id' => 'tag:1', 'answer' => 'yes' }]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_suggestion!(question_text:, answer: 'yes')
|
||||||
|
GekanatorQuestionSuggestion.create!(
|
||||||
|
gekanator_game: game,
|
||||||
|
user: user,
|
||||||
|
question_text: question_text,
|
||||||
|
answer: answer
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'converts title-contains suggestions to pending ai-generated questions' do
|
||||||
|
suggestion = create_suggestion!(question_text: '題名に「結束バンド」が含まれる?')
|
||||||
|
|
||||||
|
expect {
|
||||||
|
described_class.call(suggestion: suggestion, user: user)
|
||||||
|
}.to change { GekanatorQuestion.count }.by(1)
|
||||||
|
.and change { GekanatorAiRun.count }.by(1)
|
||||||
|
|
||||||
|
question = GekanatorQuestion.last
|
||||||
|
expect(question).to have_attributes(
|
||||||
|
text: '題名に「結束バンド」が含まれる?',
|
||||||
|
kind: 'title',
|
||||||
|
source: 'ai_generated',
|
||||||
|
status: 'pending',
|
||||||
|
priority_weight: 0.95,
|
||||||
|
gekanator_question_suggestion_id: suggestion.id,
|
||||||
|
created_by_id: user.id
|
||||||
|
)
|
||||||
|
expect(question.condition).to include(
|
||||||
|
'type' => 'title-contains',
|
||||||
|
'text' => '結束バンド'
|
||||||
|
)
|
||||||
|
expect(GekanatorAiRun.last).to have_attributes(
|
||||||
|
gekanator_question_suggestion_id: suggestion.id,
|
||||||
|
model: 'heuristic_converter_v1',
|
||||||
|
status: 'succeeded'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'converts concrete non-unknown suggestions to post-similarity questions' do
|
||||||
|
suggestion = create_suggestion!(
|
||||||
|
question_text: '喜多ちゃんが泣いてる?',
|
||||||
|
answer: 'partial'
|
||||||
|
)
|
||||||
|
|
||||||
|
question = described_class.call(suggestion: suggestion, user: user)
|
||||||
|
|
||||||
|
expect(question).to have_attributes(
|
||||||
|
text: '喜多ちゃんが泣いてる?',
|
||||||
|
kind: 'post_similarity',
|
||||||
|
source: 'ai_generated',
|
||||||
|
status: 'pending',
|
||||||
|
priority_weight: 1.0
|
||||||
|
)
|
||||||
|
expect(question.condition).to include(
|
||||||
|
'type' => 'post-similarity',
|
||||||
|
'postId' => correct_post.id,
|
||||||
|
'answer' => 'partial',
|
||||||
|
'threshold' => 0.65
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'records a failed run when the suggestion cannot be converted' do
|
||||||
|
suggestion = create_suggestion!(
|
||||||
|
question_text: 'よく分からない質問?',
|
||||||
|
answer: 'unknown'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
expect(described_class.call(suggestion: suggestion, user: user)).to be_nil
|
||||||
|
}.not_to change { GekanatorQuestion.count }
|
||||||
|
|
||||||
|
expect(GekanatorAiRun.last).to have_attributes(
|
||||||
|
gekanator_question_suggestion_id: suggestion.id,
|
||||||
|
status: 'failed'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns an existing generated question without creating a duplicate run' do
|
||||||
|
suggestion = create_suggestion!(question_text: 'タイトルは 10 文字以上?')
|
||||||
|
existing = GekanatorQuestion.create!(
|
||||||
|
text: 'タイトルは 10 文字以上?',
|
||||||
|
kind: 'title',
|
||||||
|
source: 'ai_generated',
|
||||||
|
status: 'pending',
|
||||||
|
priority_weight: 0.95,
|
||||||
|
condition: { type: 'title-length-at-least', length: 10 },
|
||||||
|
gekanator_question_suggestion: suggestion,
|
||||||
|
created_by: user
|
||||||
|
)
|
||||||
|
|
||||||
|
expect {
|
||||||
|
expect(described_class.call(suggestion: suggestion, user: user)).to eq(existing)
|
||||||
|
}.not_to change { GekanatorAiRun.count }
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -4,6 +4,7 @@ import { apiPost } from '@/lib/api'
|
|||||||
import {
|
import {
|
||||||
buildGekanatorQuestions,
|
buildGekanatorQuestions,
|
||||||
expectedAnswerForQuestion,
|
expectedAnswerForQuestion,
|
||||||
|
questionIdForCondition,
|
||||||
restoreGekanatorQuestion,
|
restoreGekanatorQuestion,
|
||||||
saveGekanatorExtraQuestionAnswers,
|
saveGekanatorExtraQuestionAnswers,
|
||||||
saveGekanatorGame,
|
saveGekanatorGame,
|
||||||
@@ -164,6 +165,27 @@ describe('expectedAnswerForQuestion', () => {
|
|||||||
|
|
||||||
expect(expectedAnswerForQuestion(question, post({ id: 1, title: 'short' }))).toBe('no')
|
expect(expectedAnswerForQuestion(question, post({ id: 1, title: 'short' }))).toBe('no')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('returns yes for matching title-contains questions', () => {
|
||||||
|
const question: StoredGekanatorQuestion = {
|
||||||
|
id: 'title:contains:結束バンド',
|
||||||
|
text: '題名に「結束バンド」が含まれる?',
|
||||||
|
kind: 'title',
|
||||||
|
condition: {
|
||||||
|
type: 'title-contains',
|
||||||
|
text: '結束バンド',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(expectedAnswerForQuestion(
|
||||||
|
question,
|
||||||
|
post({ title: '結束バンドのライブ' }),
|
||||||
|
)).toBe('yes')
|
||||||
|
expect(expectedAnswerForQuestion(
|
||||||
|
question,
|
||||||
|
post({ title: '後藤ひとりの休日' }),
|
||||||
|
)).toBe('no')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('restoreGekanatorQuestion', () => {
|
describe('restoreGekanatorQuestion', () => {
|
||||||
@@ -248,6 +270,21 @@ describe('restoreGekanatorQuestion', () => {
|
|||||||
expect(question.test(post({ title: 'x'.repeat(20) }))).toBe(false)
|
expect(question.test(post({ title: 'x'.repeat(20) }))).toBe(false)
|
||||||
expect(question.test(post({ title: 'x'.repeat(21) }))).toBe(true)
|
expect(question.test(post({ title: 'x'.repeat(21) }))).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('restores title-contains questions with a title matcher', () => {
|
||||||
|
const question = restoreGekanatorQuestion({
|
||||||
|
id: 'title:contains:結束バンド',
|
||||||
|
text: '題名に「結束バンド」が含まれる?',
|
||||||
|
kind: 'title',
|
||||||
|
condition: {
|
||||||
|
type: 'title-contains',
|
||||||
|
text: '結束バンド',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(question.test(post({ title: '結束バンドのライブ' }))).toBe(true)
|
||||||
|
expect(question.test(post({ title: '後藤ひとりの休日' }))).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('buildGekanatorQuestions', () => {
|
describe('buildGekanatorQuestions', () => {
|
||||||
@@ -264,6 +301,59 @@ describe('buildGekanatorQuestions', () => {
|
|||||||
expect(titleQuestion?.text).toMatch(/^タイトルは \d+ 文字以上\?$/)
|
expect(titleQuestion?.text).toMatch(/^タイトルは \d+ 文字以上\?$/)
|
||||||
expect(titleQuestion?.id).toMatch(/^title:length-at-least:\d+$/)
|
expect(titleQuestion?.id).toMatch(/^title:length-at-least:\d+$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('builds title-contains questions from repeated title words', () => {
|
||||||
|
const questions = buildGekanatorQuestions([
|
||||||
|
post({ id: 1, title: '結束バンド ライブ' }),
|
||||||
|
post({ id: 2, title: '結束バンド 新曲' }),
|
||||||
|
post({ id: 3, title: '後藤ひとり 練習' }),
|
||||||
|
post({ id: 4, title: '伊地知虹夏 練習' }),
|
||||||
|
])
|
||||||
|
|
||||||
|
const titleContainsQuestion = questions.find(question =>
|
||||||
|
question.condition.type === 'title-contains'
|
||||||
|
&& question.condition.text === '結束バンド')
|
||||||
|
|
||||||
|
expect(titleContainsQuestion).toMatchObject({
|
||||||
|
id: 'title:contains:結束バンド',
|
||||||
|
text: '題名に「結束バンド」が含まれる?',
|
||||||
|
kind: 'title',
|
||||||
|
source: 'default',
|
||||||
|
priorityWeight: .96,
|
||||||
|
})
|
||||||
|
expect(titleContainsQuestion?.test(post({ title: '結束バンドのライブ' }))).toBe(true)
|
||||||
|
expect(titleContainsQuestion?.test(post({ title: '廣井きくりのライブ' }))).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('honors question caps and title-contains toggles', () => {
|
||||||
|
const posts = [
|
||||||
|
post({ id: 1, title: '結束バンド ライブ' }),
|
||||||
|
post({ id: 2, title: '結束バンド 新曲' }),
|
||||||
|
post({ id: 3, title: '後藤ひとり 練習' }),
|
||||||
|
post({ id: 4, title: '伊地知虹夏 練習' }),
|
||||||
|
]
|
||||||
|
|
||||||
|
const capped = buildGekanatorQuestions(posts, {
|
||||||
|
titleContainsCap: 1,
|
||||||
|
totalQuestionCap: 1,
|
||||||
|
})
|
||||||
|
const withoutTitleContains = buildGekanatorQuestions(posts, {
|
||||||
|
includeTitleContains: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(capped).toHaveLength(1)
|
||||||
|
expect(withoutTitleContains.some(question =>
|
||||||
|
question.condition.type === 'title-contains')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('questionIdForCondition', () => {
|
||||||
|
it('builds stable ids for title-contains questions', () => {
|
||||||
|
expect(questionIdForCondition({
|
||||||
|
type: 'title-contains',
|
||||||
|
text: '結束バンド',
|
||||||
|
})).toBe('title:contains:結束バンド')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Gekanator API writers', () => {
|
describe('Gekanator API writers', () => {
|
||||||
|
|||||||
@@ -145,7 +145,30 @@ describe('recoverCandidatePosts', () => {
|
|||||||
|
|
||||||
expect(recovered?.recoveryStepCount).toBe (1)
|
expect(recovered?.recoveryStepCount).toBe (1)
|
||||||
expect([...(recovered?.recoveredCandidatePosts.keys () ?? [])])
|
expect([...(recovered?.recoveredCandidatePosts.keys () ?? [])])
|
||||||
.toEqual ([8, 7, 6, 5, 4, 3, 2])
|
.toEqual ([8, 7, 6, 5, 4])
|
||||||
expect(recovered?.recoveredCandidatePosts.get (7)).toBe (2)
|
expect(recovered?.recoveredCandidatePosts.get (7)).toBe (2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('does not add posts when recovered and eligible candidates already hit the target', () => {
|
||||||
|
const posts = Array.from ({ length: 10 }, (_value, index) => post (index + 1))
|
||||||
|
const scores = new Map (posts.map (candidate => [candidate.id, candidate.id]))
|
||||||
|
|
||||||
|
const recovered = recoverCandidatePosts ({
|
||||||
|
posts,
|
||||||
|
scores,
|
||||||
|
rejectedPostIds: new Set (),
|
||||||
|
recoveredCandidatePosts: new Map ([
|
||||||
|
[1, 1],
|
||||||
|
[2, 1],
|
||||||
|
[3, 1],
|
||||||
|
]),
|
||||||
|
eligiblePostIds: new Set ([4, 5, 6]),
|
||||||
|
answerCountAtRecovery: 2,
|
||||||
|
recoveryStepCount: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(recovered?.recoveryStepCount).toBe (1)
|
||||||
|
expect([...(recovered?.recoveredCandidatePosts.keys () ?? [])])
|
||||||
|
.toEqual ([1, 2, 3])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする