api docs: add deprecation functionality + documentation

Adds the @deprecated_method, @deprecated_argument, and
@deprecated_response_field tags for API documentation. Also adds the
'deprecated', 'deprecation_notice', 'deprecation_effective', and
'deprecation_description' attributes for the API models and their
properties.

closes GRADE-1450

Test Plan:
1. Read the deprecation_README provided in this commit and verify you
   can deprecate methods, arguments, response fields, models, and
   model attributes. This verificiation process should include:
   - Verifying the `rake doc:api` command throws an error when invalid
     and/or missing parameters are used for the deprecation tags
   - Verifying the deprecations show up in the generated documentation
     (located at public/doc/api/index.html) after running
     `rake doc:api`.
2. Verify the @argument, @response_field, and @model tags still work
   and generate appropriate documentation when running `rake doc:api`

Change-Id: If6c2b0ee9fccbbd202990d8835c6266017e2dfa5
Reviewed-on: https://gerrit.instructure.com/138581
Reviewed-by: Gary Mei <gmei@instructure.com>
Reviewed-by: Derek Bender <djbender@instructure.com>
QA-Review: Gary Mei <gmei@instructure.com>
Tested-by: Jenkins
Product-Review: Simon Williams <simon@instructure.com>
This commit is contained in:
Spencer Olson 2018-01-17 14:18:08 -06:00
parent 91820598a9
commit 3792afc12e
24 changed files with 1383 additions and 48 deletions

117
doc/DEPRECATION.md Normal file
View File

@ -0,0 +1,117 @@
# Canvas API Deprecation
In the examples below, the deprecation dates should follow these rules:
* The `NOTICE` date should be the date that the deprecation warning
will first be visible in production.
* To determine the `EFFECTIVE` date, add 90 days to the `NOTICE`
date. If that day is a production release date, use that date. If that
date is _not_ a production release date, use the next production release
date after that date.
* Both dates should be formatted as YYYY-MM-DD.
## API Method Deprecation
To deprecate an API method, use the `@deprecated_method` tag. You must provide
a `NOTICE` date and an `EFFECTIVE` date for the deprecation, along with a
description for the deprecation.
### Deprecate a method with a replacement
```ruby
# @deprecated_method NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the deprecated method and why we're deprecating it.
# Use {api:FooController#bar_action Foo#bar_action} instead.
def foo_action
end
def bar_action
end
```
### Deprecate a method without a replacement
```ruby
# @deprecated_method NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the deprecated method and why we're deprecating it.
def foo_action
end
```
## API Model Deprecation
To deprecate an API model, add the `deprecated`, `deprecation_notice`,
`deprecation_effective`, and `deprecation_description` keys. These keys can be
applied at the base model level to deprecate the entire model, or they can be
applied at the property level to individually deprecate model properties.
### Deprecate an entire API model
```ruby
# @model Foo
# {
# "id": "Foo",
# "description": "A description.",
# "deprecated": true,
# "deprecation_notice": "YYYY-MM-DD",
# "deprecation_effective": "YYYY-MM-DD",
# "deprecation_description": "A description of the deprecation.",
# "properties": {
# "bar": {
# "description": "A property.",
# "example": "baz",
# "type": "string"
# }
# }
# }
```
### Deprecate an API model property
```ruby
# @model Foo
# {
# "id": "Foo",
# "description": "A description.",
# "properties": {
# "bar": {
# "deprecated": true,
# "deprecation_notice": "YYYY-MM-DD",
# "deprecation_effective": "YYYY-MM-DD",
# "deprecation_description": "A description of the deprecation.",
# "description": "A property.",
# "example": "baz",
# "type": "string"
# }
# }
# }
```
## API Argument Deprecation
To deprecate an API argument, rename the `@argument` tag to
`@deprecated_argument`. You must provide a `NOTICE` date and an `EFFECTIVE` date
for the deprecation, along with a description for the deprecation.
Before:
```ruby
# @argument foo [Required, String]
# A description of the argument.
```
After:
```ruby
# @deprecated_argument foo [Required, String] NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the argument, along with a description of the deprecation.
```
## API Response Field Deprecation
To deprecate an API response field, rename the `@response_field` tag to
`@deprecated_response_field`. You must provide a `NOTICE` date and an
`EFFECTIVE` date for the deprecation, along with a description for the
deprecation.
Before:
```ruby
# @response_field foo
# A description of the response field.
```
After:
```ruby
# @deprecated_response_field foo NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the response field, along with a description of the
# deprecation.
```

View File

@ -46,8 +46,20 @@ end
# Extend YARD to generate our API documentation
YARD::Tags::Library.define_tag("Is an API method", :API)
YARD::Tags::Library.define_tag("API method argument", :argument)
# Expected Format:
# @deprecated_argument argument_name NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the deprecation. (required)
YARD::Tags::Library.define_tag("API method argument is deprecated", :deprecated_argument)
# Expected Format:
# @deprecated_method NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# Use {api:FooController#some_action Foo#some_action} instead. A description of the deprecated method. (required)
YARD::Tags::Library.define_tag("API method is deprecated", :deprecated_method)
YARD::Tags::Library.define_tag("API response field", :request_field)
YARD::Tags::Library.define_tag("API response field", :response_field)
# Expected Format:
# @deprecated_response_field response_field NOTICE YYYY-MM-DD EFFECTIVE YYYY-MM-DD
# A description of the deprecation. (required)
YARD::Tags::Library.define_tag("API response field is deprecated", :deprecated_response_field)
YARD::Tags::Library.define_tag("API example request", :example_request)
YARD::Tags::Library.define_tag("API example response", :example_response)
YARD::Tags::Library.define_tag("API subtopic", :subtopic)

View File

@ -36,6 +36,11 @@ p {
font-family: monospace;
}
.note.deprecated { background: #ffe5e5; border-color: #e9dada; padding: 1.33em; }
.note.title.deprecated { background: #ffe5e5; border-color: #e9dada; }
.request-param.deprecated, .response-field.deprecated { background: #ffe5e5; border-color: #e9dada; }
.param-deprecated { width: 185px; }
h1 a:link, h1 a:visited,
h2 a:link, h2 a:visited,
h3 a:link, h3 a:visited,
@ -362,36 +367,38 @@ code.enum {
padding: 12px;
font-size: 1.1em;
}
table.request-params {
table.request-params, table.response-fields {
table-layout: fixed;
width: 100%;
margin-bottom: 1.33em;
}
table.request-params, table.request-params th, table.request-params td {
table.request-params, table.request-params th, table.request-params td, table.response-fields, table.response-fields th, table.response-fields td {
border: 0;
text-align: left;
}
table.request-params td {
table.request-params td, table.response-fields td {
border-bottom: 1px solid #ccc;
vertical-align: text-top;
font-family: Monaco, Lucida Console, monospace;
font-size: 12px;
word-wrap: break-word;
}
table.request-params td.param-desc {
table.request-params td.param-desc, table.response-fields td.param-desc {
font-family: "Helvetica Neue", Helvetica;
}
table.request-params td.param-desc ul {
table.request-params td.param-desc ul, table.response-fields td.param-desc ul {
padding: 0;
list-style-position: inside;
}
.param-name {
.param-name, .field-name {
width: 20em;
}
.param-req {
width: 5em;
}
.param-type {
.param-type, .param-deprecated, .param-effective, .field-type, .field-deprecated, .field-effective, {
width: 10em;
}
.param-desc {

View File

@ -122,6 +122,8 @@ module YARD::Templates::Helpers::BaseHelper
end
module YARD::Templates::Helpers::HtmlHelper
include CanvasAPI::Deprecatable
def topicize(str)
str.gsub(' ', '_').underscore
end

View File

@ -16,16 +16,23 @@
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative 'canvas_api/deprecatable'
require 'hash_view'
class ArgumentView < HashView
attr_reader :line, :http_verb, :path_variables
include CanvasAPI::Deprecatable
attr_reader :line, :http_verb, :path_variables, :effective_date, :notice_date
attr_reader :name, :type, :desc
DEFAULT_TYPE = "[String]"
DEFAULT_DESC = "no description"
def initialize(line, http_verb = "get", path_variables = [])
def initialize(line, http_verb = "get", path_variables = [], deprecated: false)
@deprecated = deprecated
@deprecated_date_key = :NOTICE
@effective_date_key = :EFFECTIVE
@tag_declaration_line = line
@line, @name, @type, @desc = parse_line(line)
@http_verb = http_verb
@path_variables = path_variables
@ -33,7 +40,14 @@ class ArgumentView < HashView
end
def parse_line(line)
name, remaining = (line || "").split(/\s/, 2)
if deprecated?
parse_deprecation_info(line)
name_and_remaining = line_without_deprecation_tags(line)
else
name_and_remaining = line
end
name, remaining = (name_and_remaining || "").split(/\s/, 2)
raise(ArgumentError, "param name missing:\n#{line}") unless name
name.strip!
type, desc = split_type_desc(remaining || "")
@ -138,6 +152,7 @@ class ArgumentView < HashView
"type" => swagger_type,
"format" => swagger_format,
"required" => required?,
"deprecated" => deprecated?,
}
swagger['enum'] = enums unless enums.empty?
if array?

View File

@ -0,0 +1,119 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
module CanvasAPI
module Deprecatable
def deprecated?
!!@deprecated
end
private
def parse_deprecation_info(line)
validate_line(line)
dates = extract_deprecation_dates(line)
validate_deprecation_dates(dates)
@effective_date = dates[:EFFECTIVE]
@notice_date = dates[:NOTICE]
end
def line_without_deprecation_tags(line)
line.gsub(/NOTICE \S+\s?|\s?EFFECTIVE \S+\s?/, '')
end
def extract_deprecation_dates(line)
args = line.split(/\s/)
deprecation_keys = [@deprecated_date_key, @effective_date_key]
dates = {}
args.each_with_index do |arg, index|
key = arg.to_sym
next unless deprecation_keys.include?(key)
dates[key] = args[index + 1]
end
dates
end
def validate_deprecation_dates(provided_dates)
date_keys = [@deprecated_date_key, @effective_date_key]
date_keys.each { |key| validate_date(key, provided_dates) }
validate_date_range(provided_dates)
end
def reference_line
return @tag_declaration_line if @tag_declaration_line.blank?
"\n #{@tag_declaration_line}"
end
def validate_deprecation_description
if @description.blank?
raise(
ArgumentError,
"Expected a description for #{@description_key.present? ? "`#{@description_key}`": 'the deprecation'}" \
", but it was not provided.#{reference_line}"
)
end
end
def validate_line(text)
line_count = (text || '').split("\n", 2).length
if line_count < 2
raise(
ArgumentError,
"Expected two lines: a tag declaration line with deprecation arguments, " \
"and a description line, but found #{line_count} #{'line'.pluralize(line_count)}.#{reference_line}"
)
end
end
def validate_date(key, provided_dates)
if !provided_dates.key?(key)
raise(ArgumentError, "Expected argument `#{key}`, but it was not provided.#{reference_line}")
elsif provided_dates.fetch(key).blank?
raise(
ArgumentError,
"Expected a value to be present for argument `#{key}`, but it was blank.#{reference_line}"
)
end
date = provided_dates[key]
begin
Date.iso8601(date)
rescue ArgumentError
raise(
ArgumentError,
"Expected date `#{date}` for key `#{key}` to be in ISO 8601 format (YYYY-MM-DD).#{reference_line}"
)
end
end
def validate_date_range(provided_dates)
deprecated_date = provided_dates[@deprecated_date_key]
effective_date = provided_dates[@effective_date_key]
days_deprecated = Date.iso8601(effective_date) - Date.iso8601(deprecated_date)
if days_deprecated < 90
raise(
ArgumentError,
"Expected >= 90 days between the `#{@deprecated_date_key}` (#{deprecated_date}) " \
"and `#{@effective_date_key}` (#{effective_date}) dates, but the actual " \
"difference was #{days_deprecated.to_i} days.#{reference_line}"
)
end
end
end
end

View File

@ -0,0 +1,42 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative 'canvas_api/deprecatable'
require 'hash_view'
class DeprecatedMethodView < HashView
include CanvasAPI::Deprecatable
attr_reader :description, :effective_date, :notice_date
def initialize(tag)
@deprecated_date_key = :NOTICE
@effective_date_key = :EFFECTIVE
@tag_declaration_line = tag.text
@tag_type = "method"
parse_line(tag.text)
end
private
def parse_line(text)
description = (text || '').split("\n", 2).second
@description = description && format(description.strip)
parse_deprecation_info(text)
end
end

View File

@ -20,6 +20,8 @@ require 'hash_view'
require 'argument_view'
require 'route_view'
require 'return_view'
require 'response_field_view'
require 'deprecated_method_view'
class MethodView < HashView
def initialize(method)
@ -52,8 +54,22 @@ class MethodView < HashView
format(@method.docstring)
end
def deprecated?
select_tags('deprecated_method').any?
end
def deprecation_description
tag = select_tags('deprecated_method').first
description = tag ? DeprecatedMethodView.new(tag).description : ''
format(description)
end
def raw_arguments
select_tags("argument")
select_tags(['argument', 'deprecated_argument'])
end
def raw_response_fields
select_tags(['response_field', 'deprecated_response_field'])
end
def return_tag
@ -116,10 +132,11 @@ class MethodView < HashView
end
end
protected
def select_tags(tag_name)
protected
def select_tags(tag_names)
names = Array.wrap(tag_names)
@method.tags.select do |tag|
tag.tag_name.downcase == tag_name
names.include?(tag.tag_name.downcase)
end
end

View File

@ -22,20 +22,31 @@ require 'json'
class ModelView < HashView
attr_reader :name, :properties, :description, :required
def initialize(name, properties, description = "", required = [])
def initialize(name, properties, description = "", required = [], deprecated: false, deprecation_description: '')
@name = name
@properties = properties
@description = description
@required = required
@deprecated = deprecated
@deprecation_description = deprecation_description
end
def self.new_from_model(model)
lines = model.text.lines.to_a
json = JSON::parse(lines[1..-1].join)
new(lines[0].strip,
json["properties"],
json["description"] ? json["description"] : "",
json["required"] ? json["required"] : [])
new(
lines[0].strip,
json["properties"],
json["description"] ? json["description"] : "",
json["required"] ? json["required"] : [],
deprecated: json["deprecated"],
deprecation_description: json["deprecation_description"]
)
end
def deprecated?
!!@deprecated
end
def json_schema
@ -44,6 +55,8 @@ class ModelView < HashView
"id" => name,
"description" => description,
"required" => required,
"deprecated" => deprecated?,
"deprecation_description" => format(@deprecation_description),
"properties" => properties
}
}

View File

@ -0,0 +1,68 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative 'canvas_api/deprecatable'
require 'hash_view'
class ResponseFieldView < HashView
include CanvasAPI::Deprecatable
attr_reader :types, :effective_date, :notice_date
def initialize(tag)
line = tag.text
@deprecated = tag.tag_name == 'deprecated_response_field'
@deprecated_date_key = :NOTICE
@effective_date_key = :EFFECTIVE
@tag_declaration_line = line
@name, @description = parse_line(line)
@types = tag.types
end
def parse_line(line)
if deprecated?
parse_deprecation_info(line)
name_and_remaining = line_without_deprecation_tags(line)
else
name_and_remaining = line
end
name, remaining = (name_and_remaining || "").split(/\s+/, 2)
raise(ArgumentError, "param name missing:\n#{line}") unless name
raise(ArgumentError, "Expected a description to be present, but it was not provided.\n#{line}") if remaining.nil?
description = remaining.strip
[name, description]
end
def name
format(@name)
end
def description
format(@description)
end
def to_swagger
{
"name" => name,
"description" => description,
"deprecated" => deprecated?
}
end
end

View File

@ -63,7 +63,7 @@ class RouteView < HashView
def query_args
method_view.raw_arguments.map do |tag|
ArgumentView.new(tag.text, verb, path_variables)
ArgumentView.new(tag.text, verb, path_variables, deprecated: tag.tag_name&.downcase == 'deprecated_argument')
end
end
@ -85,6 +85,10 @@ class RouteView < HashView
arguments.map { |arg| arg.to_swagger }
end
def response_fields
method_view.raw_response_fields.map { |tag| ResponseFieldView.new(tag).to_swagger }
end
def nickname
method_view.nickname + method_view.unique_nickname_suffix(self)
end
@ -96,6 +100,9 @@ class RouteView < HashView
"notes" => method_view.desc,
"nickname" => nickname,
"parameters" => parameters,
"response_fields" => response_fields,
"deprecated" => method_view.deprecated?,
"deprecation_description" => method_view.deprecation_description
}.merge(method_view.swagger_type)
end

View File

@ -7,6 +7,16 @@
<span class='defined-in'><a href="https://github.com/instructure/canvas-lms/blob/master/<%= @controller_path %>"><%= "#{@route.requirements[:controller].camelize}Controller\##{@route.requirements[:action]}" %></a></span>
<% end %>
</h2>
<% if @deprecated_method.present? %>
<div class="deprecated deprecated_method">
<div class="note deprecated">
<strong>[DEPRECATED] This method is deprecated, effective <%= h @deprecated_method.effective_date %> (notice given <%= h @deprecated_method.notice_date %>):</strong>
<%= htmlify_line @deprecated_method.description %>
</div>
</div>
<% end %>
<% if @beta %>
<h3 class='beta'>BETA: This API endpoint is not finalized, and there could be breaking changes before its final release.</h3>
<% end %>

View File

@ -26,6 +26,11 @@ def header
route = @routes.first
@method_link = "method.#{route.requirements[:controller]}.#{route.requirements[:action]}"
@beta = object.tag('beta') || object.parent.tag('beta')
if object.has_tag?(:deprecated_method)
@deprecated_method = DeprecatedMethodView.new(object.tag(:deprecated_method))
end
erb(:header)
end

View File

@ -5,12 +5,15 @@
<th class="param-name">Parameter</th>
<th class="param-req"></th>
<th class="param-type">Type</th>
<% if @request_parameters.any?(&:deprecated?) %>
<th class="param-deprecated"></th>
<% end %>
<th class="param-desc">Description</th>
</tr>
</thead>
<tbody>
<% @request_parameters.each do |param| %>
<tr class="request-param">
<tr class="request-param <%= param.deprecated? ? 'deprecated' : '' %>">
<td><%= h param.name(json: false) %></td>
<td>
<% if param.required? %>
@ -18,6 +21,16 @@
<% end %>
</td>
<td><%= h param.swagger_type %></td>
<% if @request_parameters.any?(&:deprecated?) %>
<td>
<% if param.deprecated? %>
<div><strong>[DEPRECATED]</strong></div>
<div>Effective <%= h param.effective_date %> (notice given <%= h param.notice_date %>)</div>
<% end %>
</td>
<% end %>
<td class="param-desc">
<%= htmlify param.desc %>
<% if param.enums.present? %>

View File

@ -0,0 +1,19 @@
<h4><%= YARD::Tags::Library.labels[:response_field] %>:</h4>
<ul class="response_field">
<% @response_fields.each do |field| %>
<li>
<span class='type'><%= format_types(field.types) %></span>
<span class='name'><%= h field.name %></span>
<% if field.deprecated? %>
<div class='response-field deprecated'>
<strong>[DEPRECATED] This field is deprecated, effective <%= h field.effective_date %> (notice given <%= h field.notice_date %>):</strong>
<div>
<% end %>
<% if field.description.present? %>
<%= htmlify_line(field.description) %>
<% end %>
</li>
<% end %>
</ul>

View File

@ -31,12 +31,22 @@ def request_field
end
def response_field
generic_tag :response_field
return unless object.has_tag?(:response_field) || object.has_tag?(:deprecated_response_field)
response_field_tags = object.tags.select { |tag| tag.tag_name == 'response_field' || tag.tag_name == 'deprecated_response_field' }
@response_fields = response_field_tags.map do |tag|
ResponseFieldView.new(tag)
end
erb('response_fields')
end
def argument
return unless object.has_tag?(:argument)
@request_parameters = object.tags(:argument).map { |t| ArgumentView.new(t.text) }
return unless object.has_tag?(:argument) || object.has_tag?(:deprecated_argument)
argument_tags = object.tags.select { |tag| tag.tag_name == 'argument' || tag.tag_name == 'deprecated_argument' }
@request_parameters = argument_tags.map do |tag|
ArgumentView.new(tag.text, deprecated: tag.tag_name == 'deprecated_argument')
end
erb('request_parameters')
end

View File

@ -51,12 +51,22 @@ def indent(str, amount = 2, char = ' ')
str.gsub(/^/, char * amount)
end
def render_comment(string)
if string
indent(word_wrap(string), 1, '// ')
else
""
end
def deprecation_message(property)
return '' if property.key?('properties') || !property['deprecated']
parse_swagger_model(property)
indent_chars = '// '
deprecation_title = "#{indent_chars}[DEPRECATED] This property is deprecated, effective #{property['deprecation_effective']} (notice given #{property['deprecation_notice']}):\n"
deprecation_description = indent(word_wrap(property['deprecation_description'], 80 - indent_chars.length), 1, indent_chars)
"#{deprecation_title}#{deprecation_description}"
end
def render_comment(property)
indent_chars = '// '
description = property['description'] ? indent(word_wrap(property['description'], 80 - indent_chars.length), 1, indent_chars) : ''
deprecation = deprecation_message(property)
separator = description.present? && deprecation.present? ? "//\n" : ''
"#{description}#{separator}#{deprecation}"
end
def render_value(prop)
@ -83,20 +93,32 @@ def render_value(prop)
end
def render_properties(json)
json = JSON.parse(json)
if (properties = json['properties'])
result = ''
if json['description'].present?
result << render_comment(json['description'])
result << render_comment(json)
end
result << "{\n" + indent(
properties.map do |name, prop|
render_comment(prop['description']) +
%{"#{name}": } + render_value(prop)
end.join(",\n")) +
"\n}"
properties.map do |name, prop|
render_comment(prop) +
%{"#{name}": } + render_value(prop)
end.join(",\n")
) + "\n}"
end
rescue
puts "error rendering properties for model:\n#{json}"
raise
end
def parse_swagger_model(model)
@description_key = :deprecation_description
@description = model['deprecation_description']
validate_deprecation_description
@deprecated_date_key = :deprecation_notice
@effective_date_key = :deprecation_effective
@deprecation_date = model['deprecation_notice']
@effective_date = model['deprecation_effective']
validate_deprecation_dates(deprecation_notice: @deprecation_date, deprecation_effective: @effective_date)
end

View File

@ -1,9 +1,17 @@
<%= htmlify(@docstring.strip, :markdown) %>
<% @json_objects.each do |name, json| %>
<% @json_objects.each do |name, unparsed_json| %>
<% json = JSON.parse(unparsed_json) %>
<% properties = render_properties(json) %>
<div class='object_definition'>
<h3><a name="<%= name %>"><%= name[0,1] =~ /[aeiou]/i ? "An" : "A" %> <%= name %> object looks like:</a></h3>
<pre class="example code prettyprint"><code class="language-js"><%= h properties || json %></code></pre>
<pre class="example code prettyprint"><code class="language-js"><%= h properties || unparsed_json %></code></pre>
</div>
<% if json['deprecated'] %>
<% parse_swagger_model(json) %>
<p class="note deprecated">
<strong>[DEPRECATED] The <%= h name %> model is deprecated, effective <%= h json['deprecation_effective'] %> (notice given <%= h json['deprecation_notice'] %>):</strong><br/>
<%= h json['deprecation_description'] %>
</p>
<% end %>
<% end %>

View File

@ -15,11 +15,16 @@
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require File.expand_path(File.dirname(__FILE__) + '/swagger_helper')
require 'spec_helper'
require_relative 'swagger_helper'
require 'argument_view'
describe ArgumentView do
it '#deprecated? returns false' do
view = ArgumentView.new("foo [String]")
expect(view).not_to be_deprecated
end
context "type splitter" do
let(:view) { ArgumentView.new "arg [String]" }
@ -51,10 +56,15 @@ describe ArgumentView do
context "line parser" do
let(:view) { ArgumentView.new "arg [String]" }
it "raises on missing param name" do
it "raises on missing param name and missing type" do
expect { view.parse_line("") }.to raise_error(ArgumentError)
end
# This is probably not ideal — I'm just documenting existing behavior.
it "does not raise on missing param name when type is present" do
expect { view.parse_line("[String]") }.not_to raise_error
end
it "parses without desc" do
parsed = view.parse_line("arg [String]")
expect(parsed).to eq ["arg [String]", "arg", "[String]", ArgumentView::DEFAULT_DESC]
@ -97,4 +107,184 @@ describe ArgumentView do
expect(view.required?).to be_truthy
end
end
describe '#to_swagger' do
let(:view) { ArgumentView.new("foo [String]") }
it 'includes a "deprecated" key' do
expect(view.to_swagger).to have_key 'deprecated'
end
it 'returns false for "deprecated"' do
expect(view.to_swagger.fetch('deprecated')).to be false
end
end
context 'Deprecated ArgumentView' do
let(:valid_text) { "foo [Boolean] NOTICE 2018-01-02 EFFECTIVE 2018-04-30\nA description \non multiple lines." }
let(:view) { ArgumentView.new(valid_text, deprecated: true) }
it '#deprecated? returns true' do
expect(view).to be_deprecated
end
it '#to_swagger returns true for "deprecated"' do
expect(view.to_swagger.fetch('deprecated')).to be true
end
describe '#parse_line' do
it 'parses the argument name' do
argument_name = view.parse_line(valid_text).second
expect(argument_name).to eq 'foo'
end
it 'parses the argument type' do
argument_type = view.parse_line(valid_text).third
expect(argument_type).to eq '[Boolean]'
end
it 'parses the description' do
argument_type = view.parse_line(valid_text).fourth
expect(argument_type).to eq "A description \non multiple lines."
end
it 'parses the effective deprecation date' do
view.parse_line(valid_text)
expect(view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date' do
view.parse_line(valid_text)
expect(view.notice_date).to eq '2018-01-02'
end
it 'parses the effective deprecation date when it comes before the notice date' do
text = valid_text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
view.parse_line(text)
expect(view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date when it comes after the effective date' do
text = valid_text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
view.parse_line(text)
expect(view.notice_date).to eq '2018-01-02'
end
# This is probably not ideal — I'm just documenting existing behavior.
it 'uses the argument type as the name if name is not provided' do
text = valid_text.gsub('foo ', '')
view = ArgumentView.new(text, deprecated: true)
argument_name = view.parse_line(text).second
expect(argument_name).to eq '[Boolean]'
end
context 'when a type is not provided' do
let(:text) { valid_text.gsub('[Boolean] ', '') }
let(:view) { ArgumentView.new(text, deprecated: true) }
it 'parses the argument name' do
argument_name = view.parse_line(text).second
expect(argument_name).to eq 'foo'
end
it 'uses String as the default type' do
argument_type = view.parse_line(text).third
expect(argument_type).to eq '[String]'
end
it 'parses the description' do
argument_type = view.parse_line(text).fourth
expect(argument_type).to eq "A description \non multiple lines."
end
it 'sets the effective deprecation date' do
view.parse_line(text)
expect(view.effective_date).to eq '2018-04-30'
end
it 'sets the deprecation notice date' do
view.parse_line(text)
expect(view.notice_date).to eq '2018-01-02'
end
end
context 'validations' do
it 'is invalid when the text "NOTICE" is omitted' do
expect {
ArgumentView.new(valid_text.gsub('NOTICE ', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is omitted' do
expect {
ArgumentView.new(valid_text.gsub('2018-01-02 ', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected date .+ for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "NOTICE" and the NOTICE date are omitted' do
expect {
ArgumentView.new(valid_text.gsub('NOTICE 2018-01-02 ', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is not in YYYY-MM-DD format' do
expect {
ArgumentView.new(valid_text.gsub('2018-01-02', '01-02-2018'), deprecated: true)
}.to raise_error(ArgumentError, /Expected date `01-02-2018` for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "EFFECTIVE" is omitted' do
expect {
ArgumentView.new(valid_text.gsub('EFFECTIVE ', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is omitted' do
expect {
ArgumentView.new(valid_text.gsub('2018-04-30', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected a value to be present for argument `EFFECTIVE`, but it was blank./)
end
it 'is invalid when the text "EFFECTIVE" and the EFFECTIVE date are omitted' do
expect {
ArgumentView.new(valid_text.gsub(' EFFECTIVE 2018-04-30', ''), deprecated: true)
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is not in YYYY-MM-DD format' do
expect {
ArgumentView.new(valid_text.gsub('2018-04-30', '04-30-2018'), deprecated: true)
}.to raise_error(ArgumentError, /Expected date `04-30-2018` for key `EFFECTIVE` to be in ISO 8601 format/)
end
it 'is invalid when the EFFECTIVE date is < 90 days after the NOTICE date' do
expect {
ArgumentView.new(valid_text.gsub('2018-04-30', '2018-02-26'), deprecated: true)
}.to raise_error(
ArgumentError,
/Expected >= 90 days between the `NOTICE` \(2018-01-02\) and `EFFECTIVE` \(2018-02-26\) dates/
)
end
it 'is invalid when a description is not provided' do
expect {
ArgumentView.new(valid_text.gsub("\nA description \non multiple lines.", ''), deprecated: true)
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
it 'is invalid when the description is on the same line as the other content' do
text = valid_text.gsub("\nA description \non multiple lines.", ' A description.')
expect {
ArgumentView.new(text, deprecated: true)
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
end
end
end
end

View File

@ -0,0 +1,139 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative 'swagger_helper'
require 'deprecated_method_view'
describe DeprecatedMethodView do
let(:valid_text) { "foo [Boolean] NOTICE 2018-01-02 EFFECTIVE 2018-04-30\nA description \non multiple lines." }
it 'parses the effective deprecation date' do
view = DeprecatedMethodView.new(double(text: valid_text))
expect(view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date' do
view = DeprecatedMethodView.new(double(text: valid_text))
expect(view.notice_date).to eq '2018-01-02'
end
it 'parses the description' do
view = DeprecatedMethodView.new(double(text: valid_text))
expect(view.description).to eq "A description \non multiple lines."
end
it 'parses the effective deprecation date when it comes before the notice date' do
text = valid_text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
view = DeprecatedMethodView.new(double(text: text))
expect(view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date when it comes after the effective date' do
text = valid_text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
view = DeprecatedMethodView.new(double(text: text))
expect(view.notice_date).to eq '2018-01-02'
end
context 'validations' do
it 'is invalid when the text "NOTICE" is omitted' do
text = valid_text.gsub('NOTICE ', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is omitted' do
text = valid_text.gsub('2018-01-02 ', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected date .+ for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "NOTICE" and the NOTICE date are omitted' do
text = valid_text.gsub('NOTICE 2018-01-02 ', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is not in YYYY-MM-DD format' do
text = valid_text.gsub('2018-01-02', '01-02-2018')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected date `01-02-2018` for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "EFFECTIVE" is omitted' do
text = valid_text.gsub('EFFECTIVE ', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is omitted' do
text = valid_text.gsub('2018-04-30', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected a value to be present for argument `EFFECTIVE`, but it was blank./)
end
it 'is invalid when the text "EFFECTIVE" and the EFFECTIVE date are omitted' do
text = valid_text.gsub(' EFFECTIVE 2018-04-30', '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is not in YYYY-MM-DD format' do
text = valid_text.gsub('2018-04-30', '04-30-2018')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(ArgumentError, /Expected date `04-30-2018` for key `EFFECTIVE` to be in ISO 8601 format/)
end
it 'is invalid when the EFFECTIVE date is < 90 days after the NOTICE date' do
text = valid_text.gsub('2018-04-30', '2018-02-26')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(
ArgumentError,
/Expected >= 90 days between the `NOTICE` \(2018-01-02\) and `EFFECTIVE` \(2018-02-26\) dates/
)
end
it 'is invalid when a description is not provided' do
text = valid_text.gsub("\nA description \non multiple lines.", '')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
it 'is invalid when the description is on the same line as the other content' do
text = valid_text.gsub("\nA description \non multiple lines.", ' A description.')
expect {
DeprecatedMethodView.new(double(text: text))
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
end
end

View File

@ -0,0 +1,103 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative 'swagger_helper'
require 'method_view'
describe MethodView do
let(:argument_tag) do
text = "foo [String]\nA description."
double(tag_name: 'argument', text: text)
end
let(:deprecated_argument_tag) do
text = "foo NOTICE 2018-01-05 EFFECTIVE 2018-05-05\nA description."
double(tag_name: 'deprecated_argument', text: text)
end
let(:response_field_tag) do
double(tag_name: 'response_field', text: 'bar A description.')
end
let(:deprecated_response_field_tag) do
double(tag_name: 'deprecated_response_field', text: "baz NOTICE 2018-01-05 EFFECTIVE 2018-05-05\nA description.")
end
let(:deprecated_method_tag) do
text = "NOTICE 2018-01-05 EFFECTIVE 2018-05-05\nA description."
double(tag_name: 'deprecated_method', text: text)
end
describe '#deprecated?' do
it 'returns true when there is a deprecated method tag' do
view = MethodView.new(double(tags: [argument_tag, deprecated_method_tag]))
expect(view).to be_deprecated
end
it 'returns false when there is not a deprecated method tag' do
view = MethodView.new(double(tags: [argument_tag]))
expect(view).not_to be_deprecated
end
end
describe '#deprecation_description' do
it 'returns an empty string when there is not a deprecated method tag' do
view = MethodView.new(double(tags: [argument_tag]))
expect(view.deprecation_description).to eq ''
end
it 'returns the deprecation description when there is a deprecated method tag' do
view = MethodView.new(double(tags: [argument_tag, deprecated_method_tag]))
expect(view.deprecation_description).to eq 'A description.'
end
end
describe '#raw_arguments' do
it 'excludes tags that are not arguments' do
view = MethodView.new(double(tags: [response_field_tag]))
expect(view.raw_arguments).not_to include response_field_tag
end
it 'includes argument tags' do
view = MethodView.new(double(tags: [argument_tag]))
expect(view.raw_arguments).to include argument_tag
end
it 'includes deprecated argument tags' do
view = MethodView.new(double(tags: [deprecated_argument_tag]))
expect(view.raw_arguments).to include deprecated_argument_tag
end
end
describe '#raw_response_fields' do
it 'excludes tags that are not response fields' do
view = MethodView.new(double(tags: [argument_tag]))
expect(view.raw_response_fields).not_to include argument_tag
end
it 'includes response_field tags' do
view = MethodView.new(double(tags: [response_field_tag]))
expect(view.raw_response_fields).to include response_field_tag
end
it 'includes deprecated response_field tags' do
view = MethodView.new(double(tags: [deprecated_response_field_tag]))
expect(view.raw_response_fields).to include deprecated_response_field_tag
end
end
end

View File

@ -20,13 +20,29 @@ require File.expand_path(File.dirname(__FILE__) + '/swagger_helper')
require 'model_view'
describe ModelView do
let(:text) { "Example\n{ \"properties\": [] }" }
let(:text) do
"Example\n{\n \"properties\": [],\n \"deprecated\": true,\n \"deprecation_description\": \"A description.\" }"
end
let(:model) { double('Model', :text => text) }
it "is created from model" do
view = ModelView.new_from_model(model)
expect(view.name).to eq "Example"
expect(view.properties).to eq []
describe '.new_from_model' do
it "is created from model" do
view = ModelView.new_from_model(model)
expect(view.name).to eq "Example"
expect(view.properties).to eq []
end
it 'parses the deprecated attribute' do
view = ModelView.new_from_model(model)
expect(view).to be_deprecated
end
it 'parses the deprecation description' do
view = ModelView.new_from_model(model)
description = view.json_schema.dig('Example', 'deprecation_description')
expect(description).to eq 'A description.'
end
end
it "generates a schema" do
@ -40,8 +56,32 @@ describe ModelView do
}
},
"description" => "",
"required" => []
"required" => [],
"deprecated" => false,
"deprecation_description" => ""
}
})
end
describe '#deprecated?' do
let(:properties) do
{
"foo" => {
"description" => "A description of the property.",
"example" => "bar",
"type" => "string"
}
}
end
it 'returns true if the model is deprecated' do
view = ModelView.new('Foo', properties, deprecated: true, deprecation_description: 'A description.')
expect(view).to be_deprecated
end
it 'returns false if the model is not deprecated' do
view = ModelView.new('Foo', properties)
expect(view).not_to be_deprecated
end
end
end

View File

@ -0,0 +1,284 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative 'swagger_helper'
require 'response_field_view'
describe ResponseFieldView do
let(:tag) do
double(tag_name: 'response_field', text: 'foo A description.', types: ['String'])
end
let(:view) { ResponseFieldView.new(tag) }
it '#deprecated? returns false' do
expect(view).not_to be_deprecated
end
it 'sets types' do
expect(view.types).to eq tag.types
end
describe '#to_swagger' do
it 'includes "name"' do
expect(view.to_swagger.fetch('name')).to eq 'foo'
end
it 'includes "description"' do
expect(view.to_swagger.fetch('description')).to eq 'A description.'
end
it 'includes "deprecated"' do
expect(view.to_swagger.fetch('deprecated')).to be false
end
end
context 'line parser' do
it 'raises on missing param name and missing description' do
expect { view.parse_line('') }.to raise_error(ArgumentError)
end
it 'raises on missing description' do
expect {
view.parse_line('foo')
}.to raise_error(ArgumentError, /Expected a description to be present, but it was not provided./)
end
end
describe '#name' do
it 'returns the name' do
expect(view.name).to eq 'foo'
end
it 'forces the encoding of the name to UTF-8' do
response_field_view = ResponseFieldView.new(
double(
tag_name: 'response_field',
text: 'foo A description.'.force_encoding('binary'),
types: ['String']
)
)
expect(response_field_view.name.encoding.name).to eq 'UTF-8'
end
end
describe '#description' do
it 'returns the description' do
expect(view.description).to eq 'A description.'
end
it 'forces the encoding of the description to UTF-8' do
response_field_view = ResponseFieldView.new(
double(
tag_name: 'response_field',
text: 'foo A description.'.force_encoding('binary'),
types: ['String']
)
)
expect(response_field_view.description.encoding.name).to eq 'UTF-8'
end
end
context 'Deprecated ResponseFieldView' do
let(:deprecated_tag) do
double(
tag_name: 'deprecated_response_field',
text: "foo NOTICE 2018-01-02 EFFECTIVE 2018-04-30\nA description \non multiple lines.",
types: ['String']
)
end
let(:deprecated_view) { ResponseFieldView.new(deprecated_tag) }
it '#deprecated? returns true' do
expect(deprecated_view).to be_deprecated
end
it '#to_swagger returns true for "deprecated"' do
expect(deprecated_view.to_swagger.fetch('deprecated')).to be true
end
describe '#parse_line' do
it 'parses the argument name' do
argument_name = deprecated_view.parse_line(deprecated_tag.text).first
expect(argument_name).to eq 'foo'
end
it 'parses the description' do
argument_type = deprecated_view.parse_line(deprecated_tag.text).second
expect(argument_type).to eq "A description \non multiple lines."
end
it 'parses the effective deprecation date' do
deprecated_view.parse_line(deprecated_tag.text)
expect(deprecated_view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date' do
deprecated_view.parse_line(deprecated_tag.text)
expect(deprecated_view.notice_date).to eq '2018-01-02'
end
it 'parses the effective deprecation date when it comes before the notice date' do
text = deprecated_tag.text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
deprecated_view.parse_line(text)
expect(deprecated_view.effective_date).to eq '2018-04-30'
end
it 'parses the deprecation notice date when it comes after the effective date' do
text = deprecated_tag.text.gsub('NOTICE 2018-01-02 EFFECTIVE 2018-04-30', 'EFFECTIVE 2018-04-30 NOTICE 2018-01-02')
deprecated_view.parse_line(text)
expect(deprecated_view.notice_date).to eq '2018-01-02'
end
context 'validations' do
it 'is invalid when the text "NOTICE" is omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('NOTICE ', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('2018-01-02 ', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected date .+ for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "NOTICE" and the NOTICE date are omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('NOTICE 2018-01-02 ', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected argument `NOTICE`/)
end
it 'is invalid when the NOTICE date is not in YYYY-MM-DD format' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('2018-01-02', '01-02-2018'),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected date `01-02-2018` for key `NOTICE` to be in ISO 8601 format/)
end
it 'is invalid when the text "EFFECTIVE" is omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('EFFECTIVE ', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('2018-04-30', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected a value to be present for argument `EFFECTIVE`, but it was blank./)
end
it 'is invalid when the text "EFFECTIVE" and the EFFECTIVE date are omitted' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub(' EFFECTIVE 2018-04-30', ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected argument `EFFECTIVE`/)
end
it 'is invalid when the EFFECTIVE date is not in YYYY-MM-DD format' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('2018-04-30', '04-30-2018'),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(ArgumentError, /Expected date `04-30-2018` for key `EFFECTIVE` to be in ISO 8601 format/)
end
it 'is invalid when the EFFECTIVE date is < 90 days after the NOTICE date' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub('2018-04-30', '2018-02-26'),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(
ArgumentError,
/Expected >= 90 days between the `NOTICE` \(2018-01-02\) and `EFFECTIVE` \(2018-02-26\) dates/
)
end
it 'is invalid when a description is not provided' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub("\nA description \non multiple lines.", ''),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
it 'is invalid when the description is on the same line as the other content' do
tag = double(
tag_name: 'deprecated_response_field',
text: deprecated_tag.text.gsub("\nA description \non multiple lines.", ' A description.'),
types: ['String']
)
expect {
ResponseFieldView.new(tag)
}.to raise_error(
ArgumentError,
/Expected two lines: a tag declaration line with deprecation arguments, and a description line/
)
end
end
end
end
end

View File

@ -0,0 +1,73 @@
#
# Copyright (C) 2018 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require_relative '../../spec_helper'
require_relative 'swagger_helper'
require 'argument_view'
require 'route_view'
require 'response_field_view'
describe RouteView do
let(:raw_route) do
double(verb: 'GET', path: double(spec: 'foo'))
end
describe '#query_args' do
let(:argument_tag) do
text = "foo [String]\nA description."
double(tag_name: 'argument', text: text)
end
let(:deprecated_argument_tag) do
text = "foo NOTICE 2018-01-05 EFFECTIVE 2018-05-05\nA description."
double(tag_name: 'deprecated_argument', text: text)
end
it 'argument views it returns respond with false to deprecated?' do
view = RouteView.new(raw_route, double(raw_arguments: [argument_tag]))
expect(view.query_args.first).not_to be_deprecated
end
it 'deprecated argument views it returns respond with true to deprecated?' do
view = RouteView.new(raw_route, double(raw_arguments: [deprecated_argument_tag]))
expect(view.query_args.first).to be_deprecated
end
end
describe '#response_fields' do
let(:response_field_tag) do
double(tag_name: 'response_field', text: 'bar A description.', types: ['String'])
end
let(:deprecated_response_field_tag) do
text = "baz NOTICE 2018-01-05 EFFECTIVE 2018-05-05\nA description."
double(tag_name: 'deprecated_response_field', text: text, types: ['String'])
end
it 'returns response fields' do
view = RouteView.new(raw_route, double(raw_response_fields: [response_field_tag]))
field = view.response_fields.first
expect(field).to eq({ "name" => "bar", "description" => "A description.", "deprecated" => false })
end
it 'returns deprecated response fields' do
view = RouteView.new(raw_route, double(raw_response_fields: [deprecated_response_field_tag]))
field = view.response_fields.first
expect(field).to eq({ "name" => "baz", "description" => "A description.", "deprecated" => true })
end
end
end