os-backlog: migrate to planka-api 0.2.0 typed returns
0.2.0 returns Planka::Types value objects instead of parsed-JSON hashes. Replace all hash access with typed accessors, rebuild Cards#snapshot output as explicit string-keyed hashes (JSON output stays byte-compatible, so skill/agent contracts are unchanged), and rewrite test fakes on real Planka::Types records. 34 runs green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014GjXD4ZDsucFZXNgVVrnZB
This commit is contained in:
parent
91c6cbebd7
commit
8df0cb7e1e
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "os-backlog",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.0",
|
||||
"description": "Uniform Planka backlog boards across projects: idempotent board-ensure (lists, labels, archive convention, Planka 2.1.1 quirk handling) and a deterministic repo-to-board routing resolver."
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ when "board-ensure"
|
|||
project = parse_project_flag(rest)
|
||||
client = build_client
|
||||
result = Backlog::BoardEnsurer.new(client: client).ensure(name, project_name: project)
|
||||
puts "board #{result.board['name']} (#{result.created ? 'created' : 'existing'}) " \
|
||||
puts "board #{result.board.name} (#{result.created ? 'created' : 'existing'}) " \
|
||||
"in project #{project}; lists created: #{result.lists_created.join(', ')}; " \
|
||||
"labels created: #{result.labels_created.join(', ')}"
|
||||
when "activate"
|
||||
|
|
@ -79,7 +79,7 @@ when "activate"
|
|||
fail_soft("activate requires a board name") unless name
|
||||
client = build_client
|
||||
board = Backlog::BoardEnsurer.new(client: client).activate(name)
|
||||
puts "activated: #{board['name']}"
|
||||
puts "activated: #{board.name}"
|
||||
when "archive"
|
||||
name = rest.shift
|
||||
fail_soft("archive requires a board name") unless name
|
||||
|
|
@ -87,7 +87,7 @@ when "archive"
|
|||
|
||||
client = build_client
|
||||
board = Backlog::BoardEnsurer.new(client: client).archive(name)
|
||||
puts "archived: #{board['name']}"
|
||||
puts "archived: #{board.name}"
|
||||
when "resolve"
|
||||
input = JSON.parse($stdin.read)
|
||||
resolver = Backlog::Resolver.new(
|
||||
|
|
@ -102,7 +102,7 @@ when "card-add"
|
|||
description = parse_flag(rest, "--description")
|
||||
client = build_client
|
||||
card = Backlog::Cards.new(client: client).add(board_name: board, title: title, description: description)
|
||||
puts "created card ##{card['id']} at Backlog on #{board}: #{card['name']}"
|
||||
puts "created card ##{card.id} at Backlog on #{board}: #{card.name}"
|
||||
when "cards"
|
||||
board = require_flag(rest, "--board", "cards")
|
||||
client = build_client
|
||||
|
|
@ -130,7 +130,7 @@ when "card-comment"
|
|||
text = require_flag(rest, "--text", "card-comment")
|
||||
client = build_client
|
||||
comment = Backlog::Cards.new(client: client).comment(card_id: card_id, text: text)
|
||||
puts "commented on card ##{card_id} (comment ##{comment['id']})"
|
||||
puts "commented on card ##{card_id} (comment ##{comment.id})"
|
||||
when nil, "-h", "--help"
|
||||
puts <<~USAGE
|
||||
usage: os-backlog <command> [options]
|
||||
|
|
|
|||
|
|
@ -54,30 +54,30 @@ module Backlog
|
|||
# archived board by that name exists.
|
||||
#
|
||||
# @param name [String] the active (non-prefixed) name to activate
|
||||
# @return [Hash] the updated board
|
||||
# @return [Types::Board] the updated board
|
||||
def activate(name)
|
||||
board = find_board_by_name(BoardSpec.archived_name(name))
|
||||
raise "no archived board named #{BoardSpec.archived_name(name).inspect} found" unless board
|
||||
|
||||
@client.boards.update(board["id"], name: name)
|
||||
@client.boards.update(board.id, name: name)
|
||||
end
|
||||
|
||||
# Rename an active board to its archived form. Only ever called on
|
||||
# explicit human request — never invoked by +ensure+.
|
||||
#
|
||||
# @param name [String] the active (non-prefixed) name to archive
|
||||
# @return [Hash] the updated board
|
||||
# @return [Types::Board] the updated board
|
||||
def archive(name)
|
||||
board = find_board_by_name(name)
|
||||
raise "no active board named #{name.inspect} found" unless board
|
||||
|
||||
@client.boards.update(board["id"], name: BoardSpec.archived_name(name))
|
||||
@client.boards.update(board.id, name: BoardSpec.archived_name(name))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_or_create_project(project_name)
|
||||
existing = @client.projects.list.find { |p| p["name"] == project_name }
|
||||
existing = @client.projects.list.find { |p| p.name == project_name }
|
||||
return existing if existing
|
||||
|
||||
project = @client.projects.create(name: project_name, type: "shared")
|
||||
|
|
@ -90,41 +90,41 @@ module Backlog
|
|||
def fix_visibility(project)
|
||||
return unless @human_user_id
|
||||
|
||||
@client.projects.add_manager(project["id"], @human_user_id)
|
||||
@client.projects.update(project["id"], ownerProjectManagerId: nil)
|
||||
@client.projects.add_manager(project.id, @human_user_id)
|
||||
@client.projects.update(project.id, ownerProjectManagerId: nil)
|
||||
end
|
||||
|
||||
def find_or_create_board(project, name)
|
||||
board = find_board_by_name(name, project: project)
|
||||
return [board, false] if board
|
||||
|
||||
created = @client.boards.create(project["id"], name: name, position: 65_536)
|
||||
created = @client.boards.create(project.id, name: name, position: 65_536)
|
||||
[created, true]
|
||||
end
|
||||
|
||||
def find_board_by_name(name, project: nil)
|
||||
projects = project ? [project] : @client.projects.list
|
||||
projects.each do |p|
|
||||
board = @client.boards.list(p["id"]).find { |b| b["name"] == name }
|
||||
board = @client.boards.list(p.id).find { |b| b.name == name }
|
||||
return board if board
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def ensure_lists(board)
|
||||
existing = @client.lists.list(board["id"]).filter_map { |l| l["name"] }
|
||||
existing = @client.lists.list(board.id).filter_map(&:name)
|
||||
created = []
|
||||
BoardSpec::LISTS.each_with_index do |list_name, index|
|
||||
next if existing.include?(list_name)
|
||||
|
||||
@client.lists.create(board["id"], name: list_name, type: "active", position: (index + 1) * 65_536)
|
||||
@client.lists.create(board.id, name: list_name, type: "active", position: (index + 1) * 65_536)
|
||||
created << list_name
|
||||
end
|
||||
created
|
||||
end
|
||||
|
||||
def ensure_labels(board)
|
||||
existing = @client.labels.list(board["id"]).filter_map { |l| l["name"] }
|
||||
existing = @client.labels.list(board.id).filter_map(&:name)
|
||||
created = []
|
||||
BoardSpec::LABEL_NAMES.each do |label_name|
|
||||
next if existing.include?(label_name)
|
||||
|
|
@ -139,7 +139,7 @@ module Backlog
|
|||
candidates = BoardSpec::LABEL_COLOR_CANDIDATES.fetch(label_name)
|
||||
last_error = nil
|
||||
candidates.each do |color|
|
||||
return @client.labels.create(board["id"], name: label_name, color: color, position: 65_536)
|
||||
return @client.labels.create(board.id, name: label_name, color: color, position: 65_536)
|
||||
rescue Planka::ValidationError => e
|
||||
last_error = e
|
||||
next
|
||||
|
|
|
|||
|
|
@ -16,83 +16,100 @@ module Backlog
|
|||
# Create a card at the board's Backlog list. Raises if the board or
|
||||
# its Backlog list doesn't exist (callers run board-ensure first).
|
||||
#
|
||||
# @return [Hash] the created card
|
||||
# @return [Planka::Types::Card] the created card
|
||||
def add(board_name:, title:, description: nil)
|
||||
board = find_board!(board_name)
|
||||
backlog = find_list!(board, "Backlog")
|
||||
attrs = { name: title, type: "project", position: next_position(board, backlog) }
|
||||
attrs[:description] = description if description
|
||||
@client.cards.create(backlog["id"], attrs)
|
||||
@client.cards.create(backlog.id, attrs)
|
||||
end
|
||||
|
||||
# Read-only snapshot of a whole board: every list in BoardSpec order
|
||||
# (plus any extras), each with its cards and their label names.
|
||||
#
|
||||
# @return [Hash] { "board" => name, "labels" => [names],
|
||||
# "lists" => [{ "name", "cards" => [{card fields + "labels"}] }] }
|
||||
# "lists" => [{ "name", "cards" => [{"id","name","labels",...card fields}] }] }
|
||||
def snapshot(board_name:)
|
||||
board = find_board!(board_name)
|
||||
included = @client.boards.get(board["id"])["included"]
|
||||
labels_by_id = (included["labels"] || []).to_h { |l| [l["id"], l["name"]] }
|
||||
card_labels = (included["cardLabels"] || []).group_by { |cl| cl["cardId"] }
|
||||
cards_by_list = (included["cards"] || []).group_by { |c| c["listId"] }
|
||||
detail = @client.boards.get(board.id)
|
||||
labels_by_id = detail.labels.to_h { |l| [l.id, l.name] }
|
||||
card_labels = detail.card_labels.group_by(&:card_id)
|
||||
cards_by_list = detail.cards.group_by(&:list_id)
|
||||
|
||||
lists = (included["lists"] || [])
|
||||
.select { |l| l["name"] } # skip built-in archive/trash lists (name: null)
|
||||
.sort_by { |l| l["position"] || Float::INFINITY }
|
||||
.map do |list|
|
||||
cards = (cards_by_list[list["id"]] || []).map do |card|
|
||||
label_names = (card_labels[card["id"]] || []).filter_map { |cl| labels_by_id[cl["labelId"]] }
|
||||
card.merge("labels" => label_names)
|
||||
lists = detail.lists
|
||||
.select(&:name) # skip built-in archive/trash lists (name: nil)
|
||||
.sort_by { |l| l.position || Float::INFINITY }
|
||||
.map do |list|
|
||||
cards = (cards_by_list[list.id] || []).map do |card|
|
||||
label_names = (card_labels[card.id] || []).filter_map { |cl| labels_by_id[cl.label_id] }
|
||||
card_hash(card, label_names)
|
||||
end
|
||||
{ "name" => list["name"], "cards" => cards }
|
||||
{ "name" => list.name, "cards" => cards }
|
||||
end
|
||||
|
||||
{ "board" => board["name"], "labels" => labels_by_id.values, "lists" => lists }
|
||||
{ "board" => board.name, "labels" => labels_by_id.values, "lists" => lists }
|
||||
end
|
||||
|
||||
# Attach a board label (by name) to a card. The only card mutation the
|
||||
# triage agent is allowed — label changes, never column moves.
|
||||
#
|
||||
# @return [Hash] the card-label join record
|
||||
# @return [Planka::Types::CardLabel] the card-label join record
|
||||
def attach_label(board_name:, card_id:, label:)
|
||||
board = find_board!(board_name)
|
||||
record = @client.labels.list(board["id"]).find { |l| l["name"] == label }
|
||||
record = @client.labels.list(board.id).find { |l| l.name == label }
|
||||
raise "board #{board_name.inspect} has no label #{label.inspect} (run board-ensure)" unless record
|
||||
|
||||
@client.labels.attach_to_card(card_id, record["id"])
|
||||
@client.labels.attach_to_card(card_id, record.id)
|
||||
end
|
||||
|
||||
# Add a comment to a card. The only card mutation the audit agent is
|
||||
# allowed.
|
||||
#
|
||||
# @return [Hash] the created comment
|
||||
# @return [Planka::Types::Comment] the created comment
|
||||
def comment(card_id:, text:)
|
||||
@client.comments.create(card_id, text: text)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Build the plain-hash card representation the CLI/JSON output emits,
|
||||
# keeping the output shape byte-compatible with the 0.1.x snapshot
|
||||
# (string keys, "labels" merged in) even though Types::Card is an
|
||||
# immutable Data object we can no longer +merge+.
|
||||
def card_hash(card, label_names)
|
||||
{
|
||||
"id" => card.id,
|
||||
"name" => card.name,
|
||||
"description" => card.description,
|
||||
"position" => card.position,
|
||||
"listId" => card.list_id,
|
||||
"boardId" => card.board_id,
|
||||
"dueDate" => card.due_date,
|
||||
"labels" => label_names
|
||||
}
|
||||
end
|
||||
|
||||
def find_board!(name)
|
||||
@client.projects.list.each do |project|
|
||||
board = @client.boards.list(project["id"]).find { |b| b["name"] == name }
|
||||
board = @client.boards.list(project.id).find { |b| b.name == name }
|
||||
return board if board
|
||||
end
|
||||
raise "no board named #{name.inspect} found (run board-ensure first)"
|
||||
end
|
||||
|
||||
def find_list!(board, list_name)
|
||||
list = @client.lists.list(board["id"]).find { |l| l["name"] == list_name }
|
||||
raise "board #{board['name'].inspect} has no #{list_name.inspect} list (run board-ensure)" unless list
|
||||
list = @client.lists.list(board.id).find { |l| l.name == list_name }
|
||||
raise "board #{board.name.inspect} has no #{list_name.inspect} list (run board-ensure)" unless list
|
||||
|
||||
list
|
||||
end
|
||||
|
||||
def next_position(board, list)
|
||||
included = @client.boards.get(board["id"])["included"]
|
||||
positions = (included["cards"] || [])
|
||||
.select { |c| c["listId"] == list["id"] }
|
||||
.filter_map { |c| c["position"] }
|
||||
detail = @client.boards.get(board.id)
|
||||
positions = detail.cards
|
||||
.select { |c| c.list_id == list.id }
|
||||
.filter_map(&:position)
|
||||
(positions.max || 0) + 65_536
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,13 +10,12 @@ class BoardEnsurerTest < Minitest::Test
|
|||
result = ensurer.ensure("llf-schema", project_name: "Dev")
|
||||
|
||||
assert result.created
|
||||
assert_equal "llf-schema", result.board["name"]
|
||||
assert_equal "llf-schema", result.board.name
|
||||
assert_equal Backlog::BoardSpec::LISTS, result.lists_created
|
||||
assert_equal Backlog::BoardSpec::LABEL_NAMES, result.labels_created
|
||||
|
||||
project = client.projects_store.first
|
||||
assert_equal "Dev", project.name
|
||||
assert_equal "shared", project.type
|
||||
end
|
||||
|
||||
def test_rerun_is_a_noop
|
||||
|
|
@ -52,8 +51,8 @@ class BoardEnsurerTest < Minitest::Test
|
|||
ensurer.ensure("llf-schema", project_name: "Dev")
|
||||
|
||||
project = client.projects_store.first
|
||||
assert_includes project.managers, "human-1"
|
||||
assert_nil project.ownerProjectManagerId
|
||||
assert_includes client.managers_for(project.id), "human-1"
|
||||
assert_nil project.owner_project_manager_id
|
||||
end
|
||||
|
||||
def test_semi_label_is_part_of_enforced_set
|
||||
|
|
@ -73,16 +72,17 @@ class BoardEnsurerTest < Minitest::Test
|
|||
refute_equal first_choice, p0.color
|
||||
end
|
||||
|
||||
|
||||
def test_activate_renames_archived_board_back
|
||||
client = FakePlankaClient.new
|
||||
ensurer = Backlog::BoardEnsurer.new(client: client, human_user_id: "human-1")
|
||||
ensurer.ensure("llf-schema", project_name: "Dev")
|
||||
board = client.boards_store.first
|
||||
board.name = "archived--llf-schema"
|
||||
client.boards.update(board.id, name: "archived--llf-schema")
|
||||
|
||||
result = ensurer.activate("llf-schema")
|
||||
|
||||
assert_equal "llf-schema", result["name"]
|
||||
assert_equal "llf-schema", result.name
|
||||
end
|
||||
|
||||
def test_archive_renames_active_board
|
||||
|
|
@ -92,7 +92,7 @@ class BoardEnsurerTest < Minitest::Test
|
|||
|
||||
result = ensurer.archive("llf-schema")
|
||||
|
||||
assert_equal "archived--llf-schema", result["name"]
|
||||
assert_equal "archived--llf-schema", result.name
|
||||
end
|
||||
|
||||
def test_ensure_never_touches_archived_boards
|
||||
|
|
|
|||
|
|
@ -14,22 +14,22 @@ class CardsTest < Minitest::Test
|
|||
card = @cards.add(board_name: "llf-schema", title: "Fix the flaky import")
|
||||
|
||||
backlog = @client.lists_store.find { |l| l.name == "Backlog" }
|
||||
assert_equal backlog.id, card["listId"]
|
||||
assert_equal "Fix the flaky import", card["name"]
|
||||
assert_equal "project", card["type"]
|
||||
assert_equal backlog.id, card.list_id
|
||||
assert_equal "Fix the flaky import", card.name
|
||||
assert_equal "project", card.type
|
||||
end
|
||||
|
||||
def test_add_positions_new_card_after_existing_backlog_cards
|
||||
first = @cards.add(board_name: "llf-schema", title: "one")
|
||||
second = @cards.add(board_name: "llf-schema", title: "two")
|
||||
|
||||
assert_operator second["position"], :>, first["position"]
|
||||
assert_operator second.position, :>, first.position
|
||||
end
|
||||
|
||||
def test_add_with_description
|
||||
card = @cards.add(board_name: "llf-schema", title: "t", description: "details here")
|
||||
|
||||
assert_equal "details here", card["description"]
|
||||
assert_equal "details here", card.description
|
||||
end
|
||||
|
||||
def test_add_raises_for_missing_board
|
||||
|
|
@ -52,7 +52,7 @@ class CardsTest < Minitest::Test
|
|||
def test_snapshot_includes_card_label_names
|
||||
card = @cards.add(board_name: "llf-schema", title: "labeled task")
|
||||
p1 = @client.labels_store.find { |l| l.name == "P1" }
|
||||
@client.attach_label(card["id"], p1.id)
|
||||
@client.attach_label(card.id, p1.id)
|
||||
|
||||
snapshot = @cards.snapshot(board_name: "llf-schema")
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class CardsTest < Minitest::Test
|
|||
def test_attach_label_by_name
|
||||
card = @cards.add(board_name: "llf-schema", title: "raw card")
|
||||
|
||||
@cards.attach_label(board_name: "llf-schema", card_id: card["id"], label: "afk-ready")
|
||||
@cards.attach_label(board_name: "llf-schema", card_id: card.id, label: "afk-ready")
|
||||
|
||||
snapshot = @cards.snapshot(board_name: "llf-schema")
|
||||
backlog = snapshot["lists"].find { |l| l["name"] == "Backlog" }
|
||||
|
|
@ -78,7 +78,7 @@ class CardsTest < Minitest::Test
|
|||
card = @cards.add(board_name: "llf-schema", title: "raw card")
|
||||
|
||||
error = assert_raises(RuntimeError) do
|
||||
@cards.attach_label(board_name: "llf-schema", card_id: card["id"], label: "nonexistent")
|
||||
@cards.attach_label(board_name: "llf-schema", card_id: card.id, label: "nonexistent")
|
||||
end
|
||||
assert_match(/no label/, error.message)
|
||||
end
|
||||
|
|
@ -86,10 +86,10 @@ class CardsTest < Minitest::Test
|
|||
def test_comment_adds_text_to_card
|
||||
card = @cards.add(board_name: "llf-schema", title: "stale card")
|
||||
|
||||
comment = @cards.comment(card_id: card["id"], text: "audit: in Doing 9 days")
|
||||
comment = @cards.comment(card_id: card.id, text: "audit: in Doing 9 days")
|
||||
|
||||
assert_equal card["id"], comment["cardId"]
|
||||
assert_equal "audit: in Doing 9 days", comment["text"]
|
||||
assert_equal card.id, comment.card_id
|
||||
assert_equal "audit: in Doing 9 days", comment.text
|
||||
assert_equal 1, @client.comments.store.size
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,26 +1,15 @@
|
|||
require "minitest/autorun"
|
||||
require "planka_api"
|
||||
require_relative "../lib/backlog"
|
||||
|
||||
# Minimal stand-ins for the planka-api gem's error hierarchy, so tests never
|
||||
# require (or need) the real gem installed — only the shape our code
|
||||
# rescues against.
|
||||
module Planka
|
||||
class Error < StandardError; end
|
||||
class ValidationError < Error; end
|
||||
end
|
||||
|
||||
module BacklogTestHelpers
|
||||
# A hand-rolled fake of the subset of Planka::Client's resource surface
|
||||
# BoardEnsurer touches: projects, boards, lists, labels. In-memory only,
|
||||
# no network. Mirrors the gem's real return shapes (string-keyed hashes).
|
||||
# BoardEnsurer/Cards touches: projects, boards, lists, labels, cards,
|
||||
# comments. In-memory only, no network. Returns the real planka-api 0.2.0
|
||||
# typed records (Planka::Types::*) so these tests exercise the same
|
||||
# accessor shapes production code does.
|
||||
class FakePlankaClient
|
||||
Project = Struct.new(:id, :name, :type, :ownerProjectManagerId, :managers, keyword_init: true)
|
||||
Board = Struct.new(:id, :project_id, :name, keyword_init: true)
|
||||
List = Struct.new(:id, :board_id, :name, :type, :position, keyword_init: true)
|
||||
Label = Struct.new(:id, :board_id, :name, :color, keyword_init: true)
|
||||
Card = Struct.new(:id, :list_id, :board_id, :name, :type, :position, :description, keyword_init: true)
|
||||
|
||||
attr_reader :projects, :boards, :lists, :labels, :cards
|
||||
attr_reader :projects, :boards, :lists, :labels, :cards, :comments
|
||||
|
||||
def initialize(reject_colors: [])
|
||||
@next_id = 0
|
||||
|
|
@ -30,6 +19,7 @@ module BacklogTestHelpers
|
|||
@labels_store = []
|
||||
@cards_store = []
|
||||
@card_labels_store = []
|
||||
@comments_store = []
|
||||
|
||||
@projects = ProjectsResource.new(self)
|
||||
@boards = BoardsResource.new(self)
|
||||
|
|
@ -39,105 +29,115 @@ module BacklogTestHelpers
|
|||
@comments = CommentsResource.new(self)
|
||||
end
|
||||
|
||||
attr_reader :comments
|
||||
|
||||
def next_id = (@next_id += 1).to_s
|
||||
|
||||
attr_reader :projects_store, :boards_store, :lists_store, :labels_store,
|
||||
:cards_store, :card_labels_store
|
||||
:cards_store, :card_labels_store, :comments_store
|
||||
|
||||
# Project managers aren't part of Types::Project (they're their own
|
||||
# join record, Types::ProjectManager) — tracked here so tests can
|
||||
# assert on the visibility-fix quirk (BoardEnsurer#fix_visibility).
|
||||
def managers_for(project_id)
|
||||
@managers_store ||= {}
|
||||
@managers_store[project_id] ||= []
|
||||
end
|
||||
|
||||
def attach_label(card_id, label_id)
|
||||
@card_labels_store << { "id" => next_id, "cardId" => card_id, "labelId" => label_id }
|
||||
record = Planka::Types::CardLabel.new(
|
||||
id: next_id, created_at: nil, updated_at: nil, card_id: card_id, label_id: label_id
|
||||
)
|
||||
@card_labels_store << record
|
||||
record
|
||||
end
|
||||
|
||||
class ProjectsResource
|
||||
def initialize(client) = @client = client
|
||||
|
||||
def list = @client.projects_store.map { |p| stringify(p) }
|
||||
def list = @client.projects_store.dup
|
||||
|
||||
def create(attrs)
|
||||
project = FakePlankaClient::Project.new(
|
||||
id: @client.next_id, name: attrs[:name], type: attrs[:type],
|
||||
ownerProjectManagerId: @client.next_id, managers: []
|
||||
project = Planka::Types::Project.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, name: attrs[:name],
|
||||
description: nil, background_type: nil, background_gradient: nil, is_hidden: nil,
|
||||
owner_project_manager_id: @client.next_id, background_image_id: nil, is_favorite: nil
|
||||
)
|
||||
@client.projects_store << project
|
||||
stringify(project)
|
||||
project
|
||||
end
|
||||
|
||||
def update(id, attrs)
|
||||
project = @client.projects_store.find { |p| p.id == id }
|
||||
project.ownerProjectManagerId = attrs[:ownerProjectManagerId] if attrs.key?(:ownerProjectManagerId)
|
||||
stringify(project)
|
||||
idx = @client.projects_store.index(project)
|
||||
updated = project
|
||||
updated = updated.with(owner_project_manager_id: attrs[:ownerProjectManagerId]) if attrs.key?(:ownerProjectManagerId)
|
||||
@client.projects_store[idx] = updated
|
||||
updated
|
||||
end
|
||||
|
||||
def add_manager(project_id, user_id)
|
||||
project = @client.projects_store.find { |p| p.id == project_id }
|
||||
project.managers << user_id
|
||||
{ "id" => @client.next_id, "projectId" => project_id, "userId" => user_id }
|
||||
@client.managers_for(project_id) << user_id
|
||||
Planka::Types::ProjectManager.new(id: @client.next_id, created_at: nil, updated_at: nil,
|
||||
project_id: project_id, user_id: user_id)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stringify(p) = { "id" => p.id, "name" => p.name, "type" => p.type }
|
||||
end
|
||||
|
||||
class BoardsResource
|
||||
def initialize(client) = @client = client
|
||||
|
||||
def list(project_id)
|
||||
@client.boards_store.select { |b| b.project_id == project_id }.map { |b| stringify(b) }
|
||||
@client.boards_store.select { |b| b.project_id == project_id }
|
||||
end
|
||||
|
||||
def create(project_id, attrs)
|
||||
board = FakePlankaClient::Board.new(id: @client.next_id, project_id: project_id, name: attrs[:name])
|
||||
board = Planka::Types::Board.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, position: attrs[:position],
|
||||
name: attrs[:name], default_view: nil, default_card_type: nil,
|
||||
limit_card_types_to_default_one: nil, always_display_card_creator: nil,
|
||||
display_card_ages: nil, expand_task_lists_by_default: nil,
|
||||
project_id: project_id, is_subscribed: nil
|
||||
)
|
||||
@client.boards_store << board
|
||||
stringify(board)
|
||||
board
|
||||
end
|
||||
|
||||
def update(id, attrs)
|
||||
board = @client.boards_store.find { |b| b.id == id }
|
||||
board.name = attrs[:name] if attrs.key?(:name)
|
||||
stringify(board)
|
||||
idx = @client.boards_store.index(board)
|
||||
updated = board
|
||||
updated = updated.with(name: attrs[:name]) if attrs.key?(:name)
|
||||
@client.boards_store[idx] = updated
|
||||
updated
|
||||
end
|
||||
|
||||
def get(id)
|
||||
board = @client.boards_store.find { |b| b.id == id }
|
||||
{
|
||||
"item" => stringify(board),
|
||||
"included" => {
|
||||
"lists" => @client.lists.list(id),
|
||||
"labels" => @client.labels.list(id),
|
||||
"cards" => @client.cards.all_for_board(id),
|
||||
"cardLabels" => @client.card_labels_store.dup
|
||||
}
|
||||
}
|
||||
Planka::Types::BoardDetail.new(
|
||||
board: board,
|
||||
lists: @client.lists.list(id),
|
||||
labels: @client.labels.list(id),
|
||||
cards: @client.cards.all_for_board(id),
|
||||
card_labels: @client.card_labels_store.dup,
|
||||
board_memberships: []
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stringify(b) = { "id" => b.id, "projectId" => b.project_id, "name" => b.name }
|
||||
end
|
||||
|
||||
class ListsResource
|
||||
def initialize(client) = @client = client
|
||||
|
||||
def list(board_id)
|
||||
@client.lists_store.select { |l| l.board_id == board_id }.map { |l| stringify(l) }
|
||||
@client.lists_store.select { |l| l.board_id == board_id }
|
||||
end
|
||||
|
||||
def create(board_id, attrs)
|
||||
raise ArgumentError, "list create requires type" unless attrs[:type]
|
||||
|
||||
list = FakePlankaClient::List.new(id: @client.next_id, board_id: board_id, name: attrs[:name],
|
||||
type: attrs[:type], position: attrs[:position])
|
||||
list = Planka::Types::List.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, type: attrs[:type],
|
||||
position: attrs[:position], name: attrs[:name], color: nil, board_id: board_id
|
||||
)
|
||||
@client.lists_store << list
|
||||
stringify(list)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stringify(l)
|
||||
{ "id" => l.id, "boardId" => l.board_id, "name" => l.name, "type" => l.type, "position" => l.position }
|
||||
list
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -148,24 +148,19 @@ module BacklogTestHelpers
|
|||
raise ArgumentError, "card create requires type" unless attrs[:type]
|
||||
|
||||
list = @client.lists_store.find { |l| l.id == list_id }
|
||||
card = FakePlankaClient::Card.new(
|
||||
id: @client.next_id, list_id: list_id, board_id: list.board_id,
|
||||
name: attrs[:name], type: attrs[:type], position: attrs[:position],
|
||||
description: attrs[:description]
|
||||
card = Planka::Types::Card.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, type: attrs[:type],
|
||||
position: attrs[:position], name: attrs[:name], description: attrs[:description],
|
||||
due_date: nil, is_due_completed: nil, stopwatch: nil, comments_total: nil,
|
||||
is_closed: nil, list_changed_at: nil, board_id: list.board_id, list_id: list_id,
|
||||
creator_user_id: nil, prev_list_id: nil, cover_attachment_id: nil, is_subscribed: nil
|
||||
)
|
||||
@client.cards_store << card
|
||||
stringify(card)
|
||||
card
|
||||
end
|
||||
|
||||
def all_for_board(board_id)
|
||||
@client.cards_store.select { |c| c.board_id == board_id }.map { |c| stringify(c) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stringify(c)
|
||||
{ "id" => c.id, "listId" => c.list_id, "boardId" => c.board_id, "name" => c.name,
|
||||
"type" => c.type, "position" => c.position, "description" => c.description }
|
||||
@client.cards_store.select { |c| c.board_id == board_id }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -176,38 +171,41 @@ module BacklogTestHelpers
|
|||
end
|
||||
|
||||
def list(board_id)
|
||||
@client.labels_store.select { |l| l.board_id == board_id }.map { |l| stringify(l) }
|
||||
@client.labels_store.select { |l| l.board_id == board_id }
|
||||
end
|
||||
|
||||
def create(board_id, attrs)
|
||||
raise Planka::ValidationError, "color #{attrs[:color]} rejected" if @reject_colors.include?(attrs[:color])
|
||||
if @reject_colors.include?(attrs[:color])
|
||||
raise Planka::ValidationError.new(status: 422,
|
||||
body: { "message" => "color #{attrs[:color]} rejected" })
|
||||
end
|
||||
|
||||
label = FakePlankaClient::Label.new(id: @client.next_id, board_id: board_id, name: attrs[:name], color: attrs[:color])
|
||||
label = Planka::Types::Label.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, position: attrs[:position],
|
||||
name: attrs[:name], color: attrs[:color], board_id: board_id
|
||||
)
|
||||
@client.labels_store << label
|
||||
stringify(label)
|
||||
label
|
||||
end
|
||||
|
||||
def attach_to_card(card_id, label_id)
|
||||
@client.attach_label(card_id, label_id)
|
||||
@client.card_labels_store.last
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stringify(l) = { "id" => l.id, "boardId" => l.board_id, "name" => l.name, "color" => l.color }
|
||||
end
|
||||
|
||||
class CommentsResource
|
||||
def initialize(client)
|
||||
@client = client
|
||||
@store = []
|
||||
end
|
||||
|
||||
attr_reader :store
|
||||
def store = @client.comments_store
|
||||
|
||||
def create(card_id, attrs)
|
||||
comment = { "id" => @client.next_id, "cardId" => card_id, "text" => attrs[:text] }
|
||||
@store << comment
|
||||
comment = Planka::Types::Comment.new(
|
||||
id: @client.next_id, created_at: nil, updated_at: nil, text: attrs[:text],
|
||||
card_id: card_id, user_id: nil
|
||||
)
|
||||
@client.comments_store << comment
|
||||
comment
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue