Merge commit 'fd284311e7
' into kb_migration
This commit is contained in:
commit
287eacf5f3
400 changed files with 4667 additions and 5387 deletions
|
@ -1,123 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Api::V2::FiltersController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
let(:scopes) { 'read:filters' }
|
||||
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it 'returns http success' do
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
let(:scopes) { 'write:filters' }
|
||||
|
||||
before do
|
||||
post :create, params: { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns a filter with keywords' do
|
||||
json = body_as_json
|
||||
expect(json[:title]).to eq 'magic'
|
||||
expect(json[:filter_action]).to eq 'hide'
|
||||
expect(json[:context]).to eq ['home']
|
||||
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
|
||||
end
|
||||
|
||||
it 'creates a filter' do
|
||||
filter = user.account.custom_filters.first
|
||||
expect(filter).to_not be_nil
|
||||
expect(filter.keywords.pluck(:keyword)).to eq ['magic']
|
||||
expect(filter.context).to eq %w(home)
|
||||
expect(filter.irreversible?).to be true
|
||||
expect(filter.expires_at).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
let(:scopes) { 'read:filters' }
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it 'returns http success' do
|
||||
get :show, params: { id: filter.id }
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT #update' do
|
||||
let(:scopes) { 'write:filters' }
|
||||
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
|
||||
|
||||
context 'when updating filter parameters' do
|
||||
before do
|
||||
put :update, params: { id: filter.id, title: 'updated', context: %w(home public) }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'updates the filter title' do
|
||||
expect(filter.reload.title).to eq 'updated'
|
||||
end
|
||||
|
||||
it 'updates the filter context' do
|
||||
expect(filter.reload.context).to eq %w(home public)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating keywords in bulk' do
|
||||
before do
|
||||
allow(redis).to receive_messages(publish: nil)
|
||||
put :update, params: { id: filter.id, keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'updates the keyword' do
|
||||
expect(keyword.reload.keyword).to eq 'updated'
|
||||
end
|
||||
|
||||
it 'sends exactly one filters_changed event' do
|
||||
expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
let(:scopes) { 'write:filters' }
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
before do
|
||||
delete :destroy, params: { id: filter.id }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'removes the filter' do
|
||||
expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,7 +24,7 @@ describe AuthorizeInteractionsController do
|
|||
it 'renders error without acct param' do
|
||||
get :show
|
||||
|
||||
expect(response).to render_template(:error)
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
|
||||
it 'renders error when account cant be found' do
|
||||
|
@ -34,7 +34,7 @@ describe AuthorizeInteractionsController do
|
|||
|
||||
get :show, params: { acct: 'acct:missing@hostname' }
|
||||
|
||||
expect(response).to render_template(:error)
|
||||
expect(response).to have_http_status(404)
|
||||
expect(service).to have_received(:call).with('missing@hostname')
|
||||
end
|
||||
|
||||
|
@ -46,7 +46,7 @@ describe AuthorizeInteractionsController do
|
|||
|
||||
get :show, params: { acct: 'http://example.com' }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to have_http_status(302)
|
||||
expect(assigns(:resource)).to eq account
|
||||
end
|
||||
|
||||
|
@ -58,52 +58,9 @@ describe AuthorizeInteractionsController do
|
|||
|
||||
get :show, params: { acct: 'acct:found@hostname' }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response).to have_http_status(302)
|
||||
expect(assigns(:resource)).to eq account
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
describe 'when signed out' do
|
||||
it 'redirects to sign in page' do
|
||||
post :create
|
||||
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when signed in' do
|
||||
let!(:user) { Fabricate(:user) }
|
||||
let(:account) { user.account }
|
||||
|
||||
before do
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it 'shows error when account not found' do
|
||||
service = instance_double(ResolveAccountService)
|
||||
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
allow(service).to receive(:call).with('user@hostname').and_return(nil)
|
||||
|
||||
post :create, params: { acct: 'acct:user@hostname' }
|
||||
|
||||
expect(response).to render_template(:error)
|
||||
end
|
||||
|
||||
it 'follows account when found' do
|
||||
target_account = Fabricate(:account)
|
||||
service = instance_double(ResolveAccountService)
|
||||
|
||||
allow(ResolveAccountService).to receive(:new).and_return(service)
|
||||
allow(service).to receive(:call).with('user@hostname').and_return(target_account)
|
||||
|
||||
post :create, params: { acct: 'acct:user@hostname' }
|
||||
|
||||
expect(account.following?(target_account)).to be true
|
||||
expect(response).to render_template(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
controller do
|
||||
describe AccountControllerConcern do
|
||||
controller(ApplicationController) do
|
||||
include AccountControllerConcern
|
||||
|
||||
def success
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
controller do
|
||||
describe ExportControllerConcern do
|
||||
controller(ApplicationController) do
|
||||
include ExportControllerConcern
|
||||
|
||||
def index
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
controller do
|
||||
describe Localized do
|
||||
controller(ApplicationController) do
|
||||
include Localized
|
||||
|
||||
def success
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
controller do
|
||||
describe RateLimitHeaders do
|
||||
controller(ApplicationController) do
|
||||
include RateLimitHeaders
|
||||
|
||||
def show
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
describe SignatureVerification do
|
||||
let(:wrapped_actor_class) do
|
||||
Class.new do
|
||||
attr_reader :wrapped_account
|
||||
|
@ -15,7 +15,7 @@ describe ApplicationController do
|
|||
end
|
||||
end
|
||||
|
||||
controller do
|
||||
controller(ApplicationController) do
|
||||
include SignatureVerification
|
||||
|
||||
before_action :require_actor_signature!, only: [:signature_required]
|
||||
|
@ -129,6 +129,37 @@ describe ApplicationController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with non-normalized URL' do
|
||||
before do
|
||||
get :success
|
||||
|
||||
fake_request = Request.new(:get, 'http://test.host/subdir/../success')
|
||||
fake_request.on_behalf_of(author)
|
||||
|
||||
request.headers.merge!(fake_request.headers)
|
||||
|
||||
allow(controller).to receive(:actor_refresh_key!).and_return(author)
|
||||
end
|
||||
|
||||
describe '#build_signed_string' do
|
||||
it 'includes the normalized request path' do
|
||||
expect(controller.send(:build_signed_string)).to start_with "(request-target): get /success\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#signed_request?' do
|
||||
it 'returns true' do
|
||||
expect(controller.signed_request?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
describe '#signed_request_actor' do
|
||||
it 'returns an account' do
|
||||
expect(controller.signed_request_account).to eq author
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with request with unparsable Date header' do
|
||||
before do
|
||||
get :success
|
||||
|
@ -202,7 +233,7 @@ describe ApplicationController do
|
|||
|
||||
request.headers.merge!(fake_request.headers)
|
||||
|
||||
stub_request(:get, 'http://localhost:5000/actor#main-key').to_raise(Mastodon::HostValidationError)
|
||||
stub_request(:get, 'http://localhost:5000/actor').to_raise(Mastodon::HostValidationError)
|
||||
end
|
||||
|
||||
describe '#signed_request?' do
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController do
|
||||
controller do
|
||||
describe UserTrackingConcern do
|
||||
controller(ApplicationController) do
|
||||
include UserTrackingConcern
|
||||
|
||||
def show
|
||||
|
|
483
spec/fixtures/requests/idn.txt
vendored
483
spec/fixtures/requests/idn.txt
vendored
|
@ -1,483 +0,0 @@
|
|||
HTTP/1.1 200 OK
|
||||
Server: nginx
|
||||
Date: Sun, 23 Apr 2017 19:37:13 GMT
|
||||
Content-Type: text/html
|
||||
Content-Length: 38111
|
||||
Last-Modified: Wed, 20 Jul 2016 02:50:52 GMT
|
||||
Connection: keep-alive
|
||||
Accept-Ranges: bytes
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1" />
|
||||
<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
|
||||
<script>
|
||||
var _hmt = _hmt || [];
|
||||
(function() {
|
||||
var hm = document.createElement("script");
|
||||
hm.src = "http://hm.baidu.com/hm.js?746c3f6346fae8612933e5921803ee32";
|
||||
var s = document.getElementsByTagName("script")[0];
|
||||
s.parentNode.insertBefore(hm, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css"/>
|
||||
<script src="js/jquery-1.11.1.min.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="js/common.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="js/carousel.js" type="text/javascript" charset="utf-8"></script>
|
||||
<title>中国域名网站</title>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<div class="head-tips" id="headTip">
|
||||
<span class="close" id="headtips-close"><img src="css/img/close.png" alt="" /></span>
|
||||
</div>
|
||||
<div class="banner-bg"></div>
|
||||
<div class="container">
|
||||
<div class="banner">
|
||||
<img src="css/img/banner.png" alt="" />
|
||||
</div>
|
||||
<div class="nav">
|
||||
<h1>名站导航</h1>
|
||||
<div class="left-btn" id="pre">
|
||||
<img src="css/img/arrow-left.png" alt="" />
|
||||
</div>
|
||||
<div class="carousel">
|
||||
<ul class="carousel-content">
|
||||
<li>
|
||||
<a href="http://中央电视台.中国" target="_blank">
|
||||
<img src="css/img/p10.png" alt="" />
|
||||
<p>中央电视台.中国</p>
|
||||
</a><a href="http://平安北京.中国" target="_blank" class="mt-4">
|
||||
<img src="css/img/p5.png" alt="" />
|
||||
<p>平安北京.中国</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://人民网.中国" target="_blank">
|
||||
<img src="css/img/p6.png" alt="" />
|
||||
<p>人民网.中国</p>
|
||||
</a><a href="http://招商银行.中国" target="_blank" class="mt-4">
|
||||
<img src="css/img/p8.png" alt="" />
|
||||
<p>招商银行.中国</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://必胜客宅急送.中国" target="_blank">
|
||||
<img src="css/img/p1.png" alt="" />
|
||||
<p>必胜客宅急送.中国</p>
|
||||
</a><a href="http://创业咖啡.中国" target="_blank" class="mt-4">
|
||||
<img src="css/img/p2.png" alt="" />
|
||||
<p>创业咖啡.中国</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://中国移动.中国" target="_blank">
|
||||
<img src="css/img/p9.png" alt="" />
|
||||
<p>中国移动.中国</p>
|
||||
</a><a href="http://海盟.中国" target="_blank" class="mt-4">
|
||||
<img src="css/img/p3.png" alt="" />
|
||||
<p>海盟.中国</p>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="http://艺龙.中国" target="_blank">
|
||||
<img src="css/img/p7.png" alt="" />
|
||||
<p>艺龙.中国</p>
|
||||
</a><a href="http://和讯.中国" target="_blank" class="mt-4">
|
||||
<img src="css/img/p4.png" alt="" />
|
||||
<p>和讯.中国</p>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="right-btn" id="next">
|
||||
<img src="css/img/arrow-right.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="all-url">
|
||||
<div class="container">
|
||||
<h1>网址大全</h1>
|
||||
<ul class="url">
|
||||
<li><a href="http://人民网.中国" target="_blank">人民网.中国</a></li>
|
||||
<li><a href="http://新华网.中国" target="_blank">新华网.中国</a></li>
|
||||
<li><a href="http://中央电视台.中国" target="_blank">中央电视台.中国</a></li>
|
||||
<li><a href="http://光明网.中国" target="_blank">光明网.中国</a></li>
|
||||
<li><a href="http://平安北京.中国" target="_blank">平安北京.中国</a></li>
|
||||
<li><a href="http://联想微博.中国" target="_blank">联想微博.中国</a></li>
|
||||
<li><a href="http://首都网警.中国" target="_blank">首都网警.中国</a></li>
|
||||
<li><a href="http://北京消防.中国" target="_blank">北京消防.中国</a></li>
|
||||
<li><a href="http://海淀公安.中国" target="_blank">海淀公安.中国</a></li>
|
||||
<li><a href="http://通州警方.中国" target="_blank">通州警方.中国</a></li>
|
||||
<li><a href="http://门头沟禁毒.中国" target="_blank">门头沟禁毒.中国</a></li>
|
||||
<li><a href="http://西部数码.中国" target="_blank">西部数码.中国</a></li>
|
||||
<li><a href="http://中央电视台.中国" target="_blank">中央电视台.中国</a></li>
|
||||
<li><a href="http://中国移动.中国" target="_blank">中国移动.中国</a></li>
|
||||
<li><a href="http://必胜宅急送.中国" target="_blank">必胜宅急送.中国</a></li>
|
||||
<li><a href="http://老正兴.中国" target="_blank">老正兴.中国</a></li>
|
||||
<li><a href="http://广州酒家.中国" target="_blank">广州酒家.中国</a></li>
|
||||
<li><a href="http://格力.中国" target="_blank">格力.中国</a></li>
|
||||
<li><a href="http://福建金爵.中国" target="_blank">福建金爵.中国</a></li>
|
||||
<li><a href="http://和信房产.中国" target="_blank">和信房产.中国</a></li>
|
||||
<li><a href="http://金爵房地产.中国" target="_blank">金爵房地产.中国</a></li>
|
||||
<li><a href="http://联泰地产.中国" target="_blank">联泰地产.中国</a></li>
|
||||
<li><a href="http://鲁商置业.中国" target="_blank">鲁商置业.中国</a></li>
|
||||
<li><a href="http://鲁商置业股份.中国" target="_blank">鲁商置业股份.中国</a></li>
|
||||
<li><a href="http://美佳华.中国" target="_blank">美佳华.中国</a></li>
|
||||
<li><a href="http://金世纪工程.中国" target="_blank">金世纪工程.中国</a></li>
|
||||
<li><a href="http://金世纪集团.中国" target="_blank">金世纪集团.中国</a></li>
|
||||
<li><a href="http://深圳金世纪.中国" target="_blank">深圳金世纪.中国</a></li>
|
||||
<li><a href="http://总部基地.中国" target="_blank">总部基地.中国</a></li>
|
||||
<li><a href="http://德律风.中国" target="_blank">德律风.中国</a></li>
|
||||
<li><a href="http://德律风物业.中国" target="_blank">德律风物业.中国</a></li>
|
||||
<li><a href="http://柯林.中国" target="_blank">柯林.中国</a></li>
|
||||
<li><a href="http://上海德律风物业.中国" target="_blank">上海德律风物业.中国</a></li>
|
||||
<li><a href="http://广东海印集团股份.中国" target="_blank">广东海印集团股份.中国</a></li>
|
||||
<li><a href="http://广东海印集团股份有限公司.中国" target="_blank">广东海印集团股份有限公司.中国</a></li>
|
||||
<li><a href="http://艺龙.中国" target="_blank">艺龙.中国</a></li>
|
||||
<li><a href="http://北京旅游信息网.中国" target="_blank">北京旅游信息网.中国</a></li>
|
||||
<li><a href="http://北京故宫博物院.中国" target="_blank">北京故宫博物院.中国</a></li>
|
||||
<li><a href="http://旅行张家界.中国" target="_blank">旅行张家界.中国</a></li>
|
||||
<li><a href="http://张家界旅游.中国" target="_blank">张家界旅游.中国</a></li>
|
||||
<li><a href="http://广州市旅游局.中国" target="_blank">广州市旅游局.中国</a></li>
|
||||
<li><a href="http://旅游在线.中国" target="_blank">旅游在线.中国</a></li>
|
||||
<li><a href="http://威海旅游集散中心.中国" target="_blank">威海旅游集散中心.中国</a></li>
|
||||
<li><a href="http://锦州旅游.中国" target="_blank">锦州旅游.中国</a></li>
|
||||
<li><a href="http://金牛湖风景旅游度假区.中国" target="_blank">金牛湖风景旅游度假区.中国</a></li>
|
||||
<li><a href="http://环球旅行社.中国" target="_blank">环球旅行社.中国</a></li>
|
||||
<li><a href="http://养鹿场.中国" target="_blank">养鹿场.中国</a></li>
|
||||
<li><a href="http://东瀛游.中国" target="_blank">东瀛游.中国</a></li>
|
||||
<li><a href="http://东瀛游旅行社.中国" target="_blank">东瀛游旅行社.中国</a></li>
|
||||
<li><a href="http://桂林游.中国" target="_blank">桂林游.中国</a></li>
|
||||
<li><a href="http://桂林之旅.中国" target="_blank">桂林之旅.中国</a></li>
|
||||
<li><a href="http://美国环球旅行社.中国" target="_blank">美国环球旅行社.中国</a></li>
|
||||
<li><a href="http://东天目山.中国" target="_blank">东天目山.中国</a></li>
|
||||
<li><a href="http://凤山寺.中国" target="_blank">凤山寺.中国</a></li>
|
||||
<li><a href="http://黄沙古渡.中国" target="_blank">黄沙古渡.中国</a></li>
|
||||
<li><a href="http://城头山.中国" target="_blank">城头山.中国</a></li>
|
||||
<li><a href="http://港游网.中国" target="_blank">港游网.中国</a></li>
|
||||
<li><a href="http://一起游.中国" target="_blank">一起游.中国</a></li>
|
||||
<li><a href="http://山水家园.中国" target="_blank">山水家园.中国</a></li>
|
||||
<li><a href="http://蒋巷村.中国" target="_blank">蒋巷村.中国</a></li>
|
||||
<li><a href="http://蒋巷村农业生态旅游.中国" target="_blank">蒋巷村农业生态旅游.中国</a></li>
|
||||
<li><a href="http://厦门海峡旅行社.中国" target="_blank">厦门海峡旅行社.中国</a></li>
|
||||
<li><a href="http://姜堰宾馆.中国" target="_blank">姜堰宾馆.中国</a></li>
|
||||
<li><a href="http://上海远洋宾馆.中国" target="_blank">上海远洋宾馆.中国</a></li>
|
||||
<li><a href="http://红栌山庄.中国" target="_blank">红栌山庄.中国</a></li>
|
||||
<li><a href="http://金牛湖风景旅游度假区.中国" target="_blank">金牛湖风景旅游度假区.中国</a></li>
|
||||
<li><a href="http://金牛湖风景区.中国" target="_blank">金牛湖风景区.中国</a></li>
|
||||
<li><a href="http://北京半岛酒店.中国" target="_blank">北京半岛酒店.中国</a></li>
|
||||
<li><a href="http://比华利山半岛酒店.中国" target="_blank">比华利山半岛酒店.中国</a></li>
|
||||
<li><a href="http://东京半岛酒店.中国" target="_blank">东京半岛酒店.中国</a></li>
|
||||
<li><a href="http://君乐酒店.中国" target="_blank">君乐酒店.中国</a></li>
|
||||
<li><a href="http://凯迪威酒店.中国" target="_blank">凯迪威酒店.中国</a></li>
|
||||
<li><a href="http://莱州酒店.中国" target="_blank">莱州酒店.中国</a></li>
|
||||
<li><a href="http://曼谷半岛酒店.中国" target="_blank">曼谷半岛酒店.中国</a></li>
|
||||
<li><a href="http://上海半岛酒店.中国" target="_blank">上海半岛酒店.中国</a></li>
|
||||
<li><a href="http://上虞国际大酒店.中国" target="_blank">上虞国际大酒店.中国</a></li>
|
||||
<li><a href="http://王府半島酒店.中国" target="_blank">王府半島酒店.中国</a></li>
|
||||
<li><a href="http://香港半岛酒店.中国" target="_blank">香港半岛酒店.中国</a></li>
|
||||
<li><a href="http://银河大酒店.中国" target="_blank">银河大酒店.中国</a></li>
|
||||
<li><a href="http://健康365.中国" target="_blank">健康365.中国</a></li>
|
||||
<li><a href="http://家天下.中国" target="_blank">家天下.中国</a></li>
|
||||
<li><a href="http://北京大学第三医院.中国" target="_blank">北京大学第三医院.中国</a></li>
|
||||
<li><a href="http://西藏阜康医药.中国" target="_blank">西藏阜康医药.中国</a></li>
|
||||
<li><a href="http://沈阳妇婴医院.中国" target="_blank">沈阳妇婴医院.中国</a></li>
|
||||
<li><a href="http://福建医科大学附属第一医院.中国" target="_blank">福建医科大学附属第一医院.中国</a></li>
|
||||
<li><a href="http://北方药业.中国" target="_blank">北方药业.中国</a></li>
|
||||
<li><a href="http://医药导报.中国" target="_blank">医药导报.中国</a></li>
|
||||
<li><a href="http://中国医药导报.中国" target="_blank">中国医药导报.中国</a></li>
|
||||
<li><a href="http://云南省医药有限公司.中国" target="_blank">云南省医药有限公司.中国</a></li>
|
||||
<li><a href="http://云南省医药.中国" target="_blank">云南省医药.中国</a></li>
|
||||
<li><a href="http://必胜宅急送.中国" target="_blank">必胜宅急送.中国</a></li>
|
||||
<li><a href="http://青岛啤酒股份有限公司.中国" target="_blank">青岛啤酒股份有限公司.中国</a></li>
|
||||
<li><a href="http://火锅面.中国" target="_blank">火锅面.中国</a></li>
|
||||
<li><a href="http://57度湘.中国" target="_blank">57度湘.中国</a></li>
|
||||
<li><a href="http://澳門佳景集團.中国" target="_blank">澳門佳景集團.中国</a></li>
|
||||
<li><a href="http://澳門佳景飲食集團.中国" target="_blank">澳門佳景飲食集團.中国</a></li>
|
||||
<li><a href="http://赤峰陈曲.中国" target="_blank">赤峰陈曲.中国</a></li>
|
||||
<li><a href="http://春宝.中国" target="_blank">春宝.中国</a></li>
|
||||
<li><a href="http://富农水稻.中国" target="_blank">富农水稻.中国</a></li>
|
||||
<li><a href="http://功德林.中国" target="_blank">功德林.中国</a></li>
|
||||
<li><a href="http://古船.中国" target="_blank">古船.中国</a></li>
|
||||
<li><a href="http://古船食品.中国" target="_blank">古船食品.中国</a></li>
|
||||
<li><a href="http://红岩村.中国" target="_blank">红岩村.中国</a></li>
|
||||
<li><a href="http://佳景飲食集團.中国" target="_blank">佳景飲食集團.中国</a></li>
|
||||
<li><a href="http://赖永初酒业.中国" target="_blank">赖永初酒业.中国</a></li>
|
||||
<li><a href="http://厉家菜.中国" target="_blank">厉家菜.中国</a></li>
|
||||
<li><a href="http://莲花岛.中国" target="_blank">莲花岛.中国</a></li>
|
||||
<li><a href="http://廖平一两酒.中国" target="_blank">廖平一两酒.中国</a></li>
|
||||
<li><a href="http://龙轩.中国" target="_blank">龙轩.中国</a></li>
|
||||
<li><a href="http://迈德乐.中国" target="_blank">迈德乐.中国</a></li>
|
||||
<li><a href="http://明记炖品.中国" target="_blank">明记炖品.中国</a></li>
|
||||
<li><a href="http://明记炖品世家.中国" target="_blank">明记炖品世家.中国</a></li>
|
||||
<li><a href="http://黔江鸡杂.中国" target="_blank">黔江鸡杂.中国</a></li>
|
||||
<li><a href="http://聖安娜餅屋.中国" target="_blank">聖安娜餅屋.中国</a></li>
|
||||
<li><a href="http://华夏茶业网.中国" target="_blank">华夏茶业网.中国</a></li>
|
||||
<li><a href="http://宅香锅.中国" target="_blank">宅香锅.中国</a></li>
|
||||
<li><a href="http://荞麦面.中国" target="_blank">荞麦面.中国</a></li>
|
||||
<li><a href="http://宅面坊.中国" target="_blank">宅面坊.中国</a></li>
|
||||
<li><a href="http://宅豆坊.中国" target="_blank">宅豆坊.中国</a></li>
|
||||
<li><a href="http://草原羔羊肉.中国" target="_blank">草原羔羊肉.中国</a></li>
|
||||
<li><a href="http://火锅饺.中国" target="_blank">火锅饺.中国</a></li>
|
||||
<li><a href="http://鸟鸡蛋.中国" target="_blank">鸟鸡蛋.中国</a></li>
|
||||
<li><a href="http://宅米饭.中国" target="_blank">宅米饭.中国</a></li>
|
||||
<li><a href="http://白野猪肉.中国" target="_blank">白野猪肉.中国</a></li>
|
||||
<li><a href="http://黑野猪肉.中国" target="_blank">黑野猪肉.中国</a></li>
|
||||
<li><a href="http://特色野猪肉.中国" target="_blank">特色野猪肉.中国</a></li>
|
||||
<li><a href="http://生态畜牧.中国" target="_blank">生态畜牧.中国</a></li>
|
||||
<li><a href="http://野豆坊.中国" target="_blank">野豆坊.中国</a></li>
|
||||
<li><a href="http://野猪牧.中国" target="_blank">野猪牧.中国</a></li>
|
||||
<li><a href="http://野猪网.中国" target="_blank">野猪网.中国</a></li>
|
||||
<li><a href="http://酷牛肉.中国" target="_blank">酷牛肉.中国</a></li>
|
||||
<li><a href="http://羔羊网.中国" target="_blank">羔羊网.中国</a></li>
|
||||
<li><a href="http://野猪肉.中国" target="_blank">野猪肉.中国</a></li>
|
||||
<li><a href="http://鸟鸡肉.中国" target="_blank">鸟鸡肉.中国</a></li>
|
||||
<li><a href="http://藏羔羊.中国" target="_blank">藏羔羊.中国</a></li>
|
||||
<li><a href="http://酷牛牧场.中国" target="_blank">酷牛牧场.中国</a></li>
|
||||
<li><a href="http://鸟鸡牧场.中国" target="_blank">鸟鸡牧场.中国</a></li>
|
||||
<li><a href="http://鸟鸡网.中国" target="_blank">鸟鸡网.中国</a></li>
|
||||
<li><a href="http://家餐馆.中国" target="_blank">家餐馆.中国</a></li>
|
||||
<li><a href="http://宅火锅.中国" target="_blank">宅火锅.中国</a></li>
|
||||
<li><a href="http://食品饮料网.中国" target="_blank">食品饮料网.中国</a></li>
|
||||
<li><a href="http://中国湿巾.中国" target="_blank">中国湿巾.中国</a></li>
|
||||
<li><a href="http://海特果菜.中国" target="_blank">海特果菜.中国</a></li>
|
||||
<li><a href="http://果菜.中国" target="_blank">果菜.中国</a></li>
|
||||
<li><a href="http://宏鑫德.中国" target="_blank">宏鑫德.中国</a></li>
|
||||
<li><a href="http://北方烧酒.中国" target="_blank">北方烧酒.中国</a></li>
|
||||
<li><a href="http://欧兰娑曼.中国" target="_blank">欧兰娑曼.中国</a></li>
|
||||
<li><a href="http://威尔富.中国" target="_blank">威尔富.中国</a></li>
|
||||
<li><a href="http://虎林老窖.中国" target="_blank">虎林老窖.中国</a></li>
|
||||
<li><a href="http://唐记食品.中国" target="_blank">唐记食品.中国</a></li>
|
||||
<li><a href="http://津恺食品.中国" target="_blank">津恺食品.中国</a></li>
|
||||
<li><a href="http://津恺.中国" target="_blank">津恺.中国</a></li>
|
||||
<li><a href="http://老中医养生.中国" target="_blank">老中医养生.中国</a></li>
|
||||
<li><a href="http://山东伟龙食品公司.中国" target="_blank">山东伟龙食品公司.中国</a></li>
|
||||
<li><a href="http://太泉蜂业.中国" target="_blank">太泉蜂业.中国</a></li>
|
||||
<li><a href="http://天鹅肉.中国" target="_blank">天鹅肉.中国</a></li>
|
||||
<li><a href="http://望湘园.中国" target="_blank">望湘园.中国</a></li>
|
||||
<li><a href="http://伟龙饼干.中国" target="_blank">伟龙饼干.中国</a></li>
|
||||
<li><a href="http://沃根葡萄酒.中国" target="_blank">沃根葡萄酒.中国</a></li>
|
||||
<li><a href="http://亚坤集团.中国" target="_blank">亚坤集团.中国</a></li>
|
||||
<li><a href="http://鱼丸.中国" target="_blank">鱼丸.中国</a></li>
|
||||
<li><a href="http://真美集团.中国" target="_blank">真美集团.中国</a></li>
|
||||
<li><a href="http://真美食品.中国" target="_blank">真美食品.中国</a></li>
|
||||
<li><a href="http://中国餐饮标识.中国" target="_blank">中国餐饮标识.中国</a></li>
|
||||
<li><a href="http://迷奇.中国" target="_blank">迷奇.中国</a></li>
|
||||
<li><a href="http://乐隆隆.中国" target="_blank">乐隆隆.中国</a></li>
|
||||
<li><a href="http://绞股蓝.中国" target="_blank">绞股蓝.中国</a></li>
|
||||
<li><a href="http://瀑布仙茗.中国" target="_blank">瀑布仙茗.中国</a></li>
|
||||
<li><a href="http://金记食品.中国" target="_blank">金记食品.中国</a></li>
|
||||
<li><a href="http://朱老六.中国" target="_blank">朱老六.中国</a></li>
|
||||
<li><a href="http://嘉太.中国" target="_blank">嘉太.中国</a></li>
|
||||
<li><a href="http://顺德堂.中国" target="_blank">顺德堂.中国</a></li>
|
||||
<li><a href="http://广味源.中国" target="_blank">广味源.中国</a></li>
|
||||
<li><a href="http://德辉食品.中国" target="_blank">德辉食品.中国</a></li>
|
||||
<li><a href="http://金龙船.中国" target="_blank">金龙船.中国</a></li>
|
||||
<li><a href="http://东方即白.中国" target="_blank">东方即白.中国</a></li>
|
||||
<li><a href="http://中山华美实业.中国" target="_blank">中山华美实业.中国</a></li>
|
||||
<li><a href="http://富士亭.中国" target="_blank">富士亭.中国</a></li>
|
||||
<li><a href="http://三安科技.中国" target="_blank">三安科技.中国</a></li>
|
||||
<li><a href="http://供美香食品.中国" target="_blank">供美香食品.中国</a></li>
|
||||
<li><a href="http://丰德天元.中国" target="_blank">丰德天元.中国</a></li>
|
||||
<li><a href="http://老藏医.中国" target="_blank">老藏医.中国</a></li>
|
||||
<li><a href="http://新农仓.中国" target="_blank">新农仓.中国</a></li>
|
||||
<li><a href="http://濠吉.中国" target="_blank">濠吉.中国</a></li>
|
||||
<li><a href="http://品味爽.中国" target="_blank">品味爽.中国</a></li>
|
||||
<li><a href="http://坤育.中国" target="_blank">坤育.中国</a></li>
|
||||
<li><a href="http://皇宫食品.中国" target="_blank">皇宫食品.中国</a></li>
|
||||
<li><a href="http://依海.中国" target="_blank">依海.中国</a></li>
|
||||
<li><a href="http://广州凯虹.中国" target="_blank">广州凯虹.中国</a></li>
|
||||
<li><a href="http://宝姿日化.中国" target="_blank">宝姿日化.中国</a></li>
|
||||
<li><a href="http://乐高乐.中国" target="_blank">乐高乐.中国</a></li>
|
||||
<li><a href="http://茂华食品.中国" target="_blank">茂华食品.中国</a></li>
|
||||
<li><a href="http://白鹿集团.中国" target="_blank">白鹿集团.中国</a></li>
|
||||
<li><a href="http://好丽友集团.中国" target="_blank">好丽友集团.中国</a></li>
|
||||
<li><a href="http://法兰红.中国" target="_blank">法兰红.中国</a></li>
|
||||
<li><a href="http://教育部.中国" target="_blank">教育部.中国</a></li>
|
||||
<li><a href="http://国家民委.中国" target="_blank">国家民委.中国</a></li>
|
||||
<li><a href="http://人口计生委.中国" target="_blank">人口计生委.中国</a></li>
|
||||
<li><a href="http://工商总局.中国" target="_blank">工商总局.中国</a></li>
|
||||
<li><a href="http://监察部.中国" target="_blank">监察部.中国</a></li>
|
||||
<li><a href="http://农业部.中国" target="_blank">农业部.中国</a></li>
|
||||
<li><a href="http://人民银行.中国" target="_blank">人民银行.中国</a></li>
|
||||
<li><a href="http://侨办.中国" target="_blank">侨办.中国</a></li>
|
||||
<li><a href="http://食品药品监督局.中国" target="_blank">食品药品监督局.中国</a></li>
|
||||
<li><a href="http://科技部.中国" target="_blank">科技部.中国</a></li>
|
||||
<li><a href="http://财政部.中国" target="_blank">财政部.中国</a></li>
|
||||
<li><a href="http://文化部.中国" target="_blank">文化部.中国</a></li>
|
||||
<li><a href="http://审计署.中国" target="_blank">审计署.中国</a></li>
|
||||
<li><a href="http://体育总局.中国" target="_blank">体育总局.中国</a></li>
|
||||
<li><a href="http://知识产权局.中国" target="_blank">知识产权局.中国</a></li>
|
||||
<li><a href="http://国研网.中国" target="_blank">国研网.中国</a></li>
|
||||
<li><a href="http://电监会.中国" target="_blank">电监会.中国</a></li>
|
||||
<li><a href="http://民航总局.中国" target="_blank">民航总局.中国</a></li>
|
||||
<li><a href="http://卫生部.中国" target="_blank">卫生部.中国</a></li>
|
||||
<li><a href="http://安全监察总局.中国" target="_blank">安全监察总局.中国</a></li>
|
||||
<li><a href="http://国家行政学院.中国" target="_blank">国家行政学院.中国</a></li>
|
||||
<li><a href="http://申银万国.中国" target="_blank">申银万国.中国</a></li>
|
||||
<li><a href="http://保定保险协会.中国" target="_blank">保定保险协会.中国</a></li>
|
||||
<li><a href="http://和讯.中国" target="_blank">和讯.中国</a></li>
|
||||
<li><a href="http://招商证券.中国" target="_blank">招商证券.中国</a></li>
|
||||
<li><a href="http://中投证券.中国" target="_blank">中投证券.中国</a></li>
|
||||
<li><a href="http://鹏元征信.中国" target="_blank">鹏元征信.中国</a></li>
|
||||
<li><a href="http://中融联合.中国" target="_blank">中融联合.中国</a></li>
|
||||
<li><a href="http://长城资产.中国" target="_blank">长城资产.中国</a></li>
|
||||
<li><a href="http://周生生證券.中国" target="_blank">周生生證券.中国</a></li>
|
||||
<li><a href="http://福建湄洲湾控股.中国" target="_blank">福建湄洲湾控股.中国</a></li>
|
||||
<li><a href="http://中安现金.中国" target="_blank">中安现金.中国</a></li>
|
||||
<li><a href="http://中安信业.中国" target="_blank">中安信业.中国</a></li>
|
||||
<li><a href="http://聯訊證券.中国" target="_blank">聯訊證券.中国</a></li>
|
||||
<li><a href="http://元富理財网.中国" target="_blank">元富理財网.中国</a></li>
|
||||
<li><a href="http://金立方资本.中国" target="_blank">金立方资本.中国</a></li>
|
||||
<li><a href="http://安信证券.中国" target="_blank">安信证券.中国</a></li>
|
||||
<li><a href="http://中国创业投资网.中国" target="_blank">中国创业投资网.中国</a></li>
|
||||
<li><a href="http://進邦匯理.中国" target="_blank">進邦匯理.中国</a></li>
|
||||
<li><a href="http://中再集团.中国" target="_blank">中再集团.中国</a></li>
|
||||
<li><a href="http://交通银行.中国" target="_blank">交通银行.中国</a></li>
|
||||
<li><a href="http://农业银行.中国" target="_blank">农业银行.中国</a></li>
|
||||
<li><a href="http://民生银行.中国" target="_blank">民生银行.中国</a></li>
|
||||
<li><a href="http://招商银行.中国" target="_blank">招商银行.中国</a></li>
|
||||
<li><a href="http://黄河银行.中国" target="_blank">黄河银行.中国</a></li>
|
||||
<li><a href="http://周口市商业银行.中国" target="_blank">周口市商业银行.中国</a></li>
|
||||
<li><a href="http://金融快线.中国" target="_blank">金融快线.中国</a></li>
|
||||
<li><a href="http://农信银.中国" target="_blank">农信银.中国</a></li>
|
||||
<li><a href="http://乐pad微博.中国" target="_blank">乐pad微博.中国</a></li>
|
||||
<li><a href="http://联想显示器.中国" target="_blank">联想显示器.中国</a></li>
|
||||
<li><a href="http://联想打印.中国" target="_blank">联想打印.中国</a></li>
|
||||
<li><a href="http://联想Z流行.中国" target="_blank">联想Z流行.中国</a></li>
|
||||
<li><a href="http://中国国际新闻网.中国" target="_blank">中国国际新闻网.中国</a></li>
|
||||
<li><a href="http://洛阳电视台.中国" target="_blank">洛阳电视台.中国</a></li>
|
||||
<li><a href="http://崇左新闻网.中国" target="_blank">崇左新闻网.中国</a></li>
|
||||
<li><a href="http://超越之路.中国" target="_blank">超越之路.中国</a></li>
|
||||
<li><a href="http://长安教育网.中国" target="_blank">长安教育网.中国</a></li>
|
||||
<li><a href="http://唐密茶道.中国" target="_blank">唐密茶道.中国</a></li>
|
||||
<li><a href="http://雷峰陪练.中国" target="_blank">雷峰陪练.中国</a></li>
|
||||
<li><a href="http://考研.中国" target="_blank">考研.中国</a></li>
|
||||
<li><a href="http://世界大学城.中国" target="_blank">世界大学城.中国</a></li>
|
||||
<li><a href="http://路正驾校.中国" target="_blank">路正驾校.中国</a></li>
|
||||
<li><a href="http://比特威.中国" target="_blank">比特威.中国</a></li>
|
||||
<li><a href="http://吉林省农业科学院.中国" target="_blank">吉林省农业科学院.中国</a></li>
|
||||
<li><a href="http://普通话审音.中国" target="_blank">普通话审音.中国</a></li>
|
||||
<li><a href="http://童帅国际教育.中国" target="_blank">童帅国际教育.中国</a></li>
|
||||
<li><a href="http://成功之钥.中国" target="_blank">成功之钥.中国</a></li>
|
||||
<li><a href="http://西安理工大学.中国" target="_blank">西安理工大学.中国</a></li>
|
||||
<li><a href="http://贵阳电脑学校.中国" target="_blank">贵阳电脑学校.中国</a></li>
|
||||
<li><a href="http://黑龙江省实验中学.中国" target="_blank">黑龙江省实验中学.中国</a></li>
|
||||
<li><a href="http://浙江艺术职业学院.中国" target="_blank">浙江艺术职业学院.中国</a></li>
|
||||
<li><a href="http://萃忆学堂.中国" target="_blank">萃忆学堂.中国</a></li>
|
||||
<li><a href="http://闽南科技学院.中国" target="_blank">闽南科技学院.中国</a></li>
|
||||
<li><a href="http://普通话语音.中国" target="_blank">普通话语音.中国</a></li>
|
||||
<li><a href="http://鞍山师范大学.中国" target="_blank">鞍山师范大学.中国</a></li>
|
||||
<li><a href="http://北京电影学院.中国" target="_blank">北京电影学院.中国</a></li>
|
||||
<li><a href="http://成都理工大学.中国" target="_blank">成都理工大学.中国</a></li>
|
||||
<li><a href="http://东北大学.中国" target="_blank">东北大学.中国</a></li>
|
||||
<li><a href="http://赣南师范学院.中国" target="_blank">赣南师范学院.中国</a></li>
|
||||
<li><a href="http://广州大学.中国" target="_blank">广州大学.中国</a></li>
|
||||
<li><a href="http://河北大学.中国" target="_blank">河北大学.中国</a></li>
|
||||
<li><a href="http://河北科技师范学院.中国" target="_blank">河北科技师范学院.中国</a></li>
|
||||
<li><a href="http://河南农业大学.中国" target="_blank">河南农业大学.中国</a></li>
|
||||
<li><a href="http://江西师范大学.中国" target="_blank">江西师范大学.中国</a></li>
|
||||
<li><a href="http://辽宁大学.中国" target="_blank">辽宁大学.中国</a></li>
|
||||
<li><a href="http://南昌大学.中国" target="_blank">南昌大学.中国</a></li>
|
||||
<li><a href="http://南京理工大学.中国" target="_blank">南京理工大学.中国</a></li>
|
||||
<li><a href="http://青岛大学.中国" target="_blank">青岛大学.中国</a></li>
|
||||
<li><a href="http://山东大学.中国" target="_blank">山东大学.中国</a></li>
|
||||
<li><a href="http://汕头大学.中国" target="_blank">汕头大学.中国</a></li>
|
||||
<li><a href="http://上海交通大学.中国" target="_blank">上海交通大学.中国</a></li>
|
||||
<li><a href="http://首都经济贸易大学.中国" target="_blank">首都经济贸易大学.中国</a></li>
|
||||
<li><a href="http://四川文理学院.中国" target="_blank">四川文理学院.中国</a></li>
|
||||
<li><a href="http://天津大学.中国" target="_blank">天津大学.中国</a></li>
|
||||
<li><a href="http://五邑大学.中国" target="_blank">五邑大学.中国</a></li>
|
||||
<li><a href="http://百色学院.中国" target="_blank">百色学院.中国</a></li>
|
||||
<li><a href="http://北京化工大学.中国" target="_blank">北京化工大学.中国</a></li>
|
||||
<li><a href="http://大连理工大学.中国" target="_blank">大连理工大学.中国</a></li>
|
||||
<li><a href="http://福建医科大学.中国" target="_blank">福建医科大学.中国</a></li>
|
||||
<li><a href="http://广东工业大学.中国" target="_blank">广东工业大学.中国</a></li>
|
||||
<li><a href="http://海南师范大学.中国" target="_blank">海南师范大学.中国</a></li>
|
||||
<li><a href="http://淮海工学院.中国" target="_blank">淮海工学院.中国</a></li>
|
||||
<li><a href="http://辽宁对外经贸学院.中国" target="_blank">辽宁对外经贸学院.中国</a></li>
|
||||
<li><a href="http://青海师范大学.中国" target="_blank">青海师范大学.中国</a></li>
|
||||
<li><a href="http://山东农业大学.中国" target="_blank">山东农业大学.中国</a></li>
|
||||
<li><a href="http://上海财经大学.中国" target="_blank">上海财经大学.中国</a></li>
|
||||
<li><a href="http://上海中医药大学.中国" target="_blank">上海中医药大学.中国</a></li>
|
||||
<li><a href="http://首都师范大学.中国" target="_blank">首都师范大学.中国</a></li>
|
||||
<li><a href="http://塔里木大学.中国" target="_blank">塔里木大学.中国</a></li>
|
||||
<li><a href="http://西安电子科技大学.中国" target="_blank">西安电子科技大学.中国</a></li>
|
||||
<li><a href="http://清华大学.中国" target="_blank">清华大学.中国</a></li>
|
||||
<li><a href="http://大连医科大学.中国" target="_blank">大连医科大学.中国</a></li>
|
||||
<li><a href="http://贵州大学.中国" target="_blank">贵州大学.中国</a></li>
|
||||
<li><a href="http://哈尔滨学院.中国" target="_blank">哈尔滨学院.中国</a></li>
|
||||
<li><a href="http://海南医学院.中国" target="_blank">海南医学院.中国</a></li>
|
||||
<li><a href="http://黑龙江大学.中国" target="_blank">黑龙江大学.中国</a></li>
|
||||
<li><a href="http://集美大学.中国" target="_blank">集美大学.中国</a></li>
|
||||
<li><a href="http://南京邮电大学.中国" target="_blank">南京邮电大学.中国</a></li>
|
||||
<li><a href="http://上海大学.中国" target="_blank">上海大学.中国</a></li>
|
||||
<li><a href="http://深圳大学.中国" target="_blank">深圳大学.中国</a></li>
|
||||
<li><a href="http://四川大学.中国" target="_blank">四川大学.中国</a></li>
|
||||
<li><a href="http://天津师范大学.中国" target="_blank">天津师范大学.中国</a></li>
|
||||
<li><a href="http://西安工业大学.中国" target="_blank">西安工业大学.中国</a></li>
|
||||
<li><a href="http://北华大学.中国" target="_blank">北华大学.中国</a></li>
|
||||
<li><a href="http://防灾科技学院.中国" target="_blank">防灾科技学院.中国</a></li>
|
||||
<li><a href="http://甘肃农业大学.中国" target="_blank">甘肃农业大学.中国</a></li>
|
||||
<li><a href="http://广西师范学院.中国" target="_blank">广西师范学院.中国</a></li>
|
||||
<li><a href="http://哈尔滨医科大学.中国" target="_blank">哈尔滨医科大学.中国</a></li>
|
||||
<li><a href="http://河北科技大学.中国" target="_blank">河北科技大学.中国</a></li>
|
||||
<li><a href="http://内蒙古大学.中国" target="_blank">内蒙古大学.中国</a></li>
|
||||
<li><a href="http://宁夏大学.中国" target="_blank">宁夏大学.中国</a></li>
|
||||
<li><a href="http://山东财经大学.中国" target="_blank">山东财经大学.中国</a></li>
|
||||
<li><a href="http://陕西师范大学.中国" target="_blank">陕西师范大学.中国</a></li>
|
||||
<li><a href="http://上海对外贸易学院.中国" target="_blank">上海对外贸易学院.中国</a></li>
|
||||
<li><a href="http://四川警察学院.中国" target="_blank">四川警察学院.中国</a></li>
|
||||
<li><a href="http://西华大学.中国" target="_blank">西华大学.中国</a></li>
|
||||
<li><a href="http://许昌学院.中国" target="_blank">许昌学院.中国</a></li>
|
||||
<li><a href="http://扬州大学.中国" target="_blank">扬州大学.中国</a></li>
|
||||
<li><a href="http://中国矿业大学.中国" target="_blank">中国矿业大学.中国</a></li>
|
||||
<li><a href="http://中南大学.中国" target="_blank">中南大学.中国</a></li>
|
||||
<li><a href="http://西安理工大学.中国" target="_blank">西安理工大学.中国</a></li>
|
||||
<li><a href="http://烟台大学.中国" target="_blank">烟台大学.中国</a></li>
|
||||
<li><a href="http://漳州师范学院.中国" target="_blank">漳州师范学院.中国</a></li>
|
||||
<li><a href="http://郑州大学.中国" target="_blank">郑州大学.中国</a></li>
|
||||
<li><a href="http://中国农业大学.中国" target="_blank">中国农业大学.中国</a></li>
|
||||
<li><a href="http://中国医药大学.中国" target="_blank">中国医药大学.中国</a></li>
|
||||
<li><a href="http://西安邮电学院.中国" target="_blank">西安邮电学院.中国</a></li>
|
||||
<li><a href="http://新疆大学.中国" target="_blank">新疆大学.中国</a></li>
|
||||
<li><a href="http://云南师范大学.中国" target="_blank">云南师范大学.中国</a></li>
|
||||
<li><a href="http://中国政法大学.中国" target="_blank">中国政法大学.中国</a></li>
|
||||
<li><a href="http://西昌学院.中国" target="_blank">西昌学院.中国</a></li>
|
||||
<li><a href="http://新疆农业大学.中国" target="_blank">新疆农业大学.中国</a></li>
|
||||
<li><a href="http://浙江万里学院.中国" target="_blank">浙江万里学院.中国</a></li>
|
||||
<li><a href="http://重庆大学.中国" target="_blank">重庆大学.中国</a></li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="open">
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1 class="Chinese-domain">中文域名简介</h1>
|
||||
<p class="Chinese-domain-content">
|
||||
“中国域名”是中文域名的一种,特指以“中国”为后缀的中文域名,是我国域名体系和全球互联网域名体系的重要组成部分。“中国”是在全球互联网上代表中国的中文顶级域名,于2010年7月正式纳入全球互联网域名体系,全球互联网域名体系,全球网民可通过联网计算机在世界任何国家和地区实现无障碍访问。“中国”域名在使用上和 .CN,相似属于互联网上的基础服务,基于域名可以提供WWW.EMAIL FTP等应用服务。
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<p>ICP备案编号:京ICP 备09112257号-68 版权所有中国互联网信息中心</p>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
$("#headTip").hide()
|
||||
var hostname = window.location.hostname || "";
|
||||
|
||||
var tips = "您所访问的域名 <font size='' color='#ff0000'>" + hostname +"</font> 无法到达,您可以尝试重新访问,或使用搜索相关信息"
|
||||
if (hostname != "导航.中国") {
|
||||
$("#headTip").html(tips);
|
||||
$("#headTip").delay(500).slideDown();
|
||||
$('#headTip').delay(5000).slideUp();
|
||||
}
|
||||
</script>
|
||||
</html>
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::ActionLogsHelper do
|
||||
end
|
|
@ -139,6 +139,35 @@ RSpec.describe ActivityPub::Activity::Flag do
|
|||
expect(report.status_ids).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an account is passed but no status' do
|
||||
let(:mentioned) { Fabricate(:account) }
|
||||
|
||||
let(:json) do
|
||||
{
|
||||
'@context': 'https://www.w3.org/ns/activitystreams',
|
||||
id: flag_id,
|
||||
type: 'Flag',
|
||||
content: 'Boo!!',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(sender),
|
||||
object: [
|
||||
ActivityPub::TagManager.instance.uri_for(flagged),
|
||||
],
|
||||
}.with_indifferent_access
|
||||
end
|
||||
|
||||
before do
|
||||
subject.perform
|
||||
end
|
||||
|
||||
it 'creates a report with no attached status' do
|
||||
report = Report.find_by(account: sender, target_account: flagged)
|
||||
|
||||
expect(report).to_not be_nil
|
||||
expect(report.comment).to eq 'Boo!!'
|
||||
expect(report.status_ids).to eq []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform with a defined uri' do
|
||||
|
|
|
@ -4,7 +4,9 @@ require 'rails_helper'
|
|||
require 'securerandom'
|
||||
|
||||
describe Request do
|
||||
subject { described_class.new(:get, 'http://example.com') }
|
||||
subject { described_class.new(:get, url) }
|
||||
|
||||
let(:url) { 'http://example.com' }
|
||||
|
||||
describe '#headers' do
|
||||
it 'returns user agent' do
|
||||
|
@ -92,6 +94,99 @@ describe Request do
|
|||
expect { subject.perform }.to raise_error Mastodon::ValidationError
|
||||
end
|
||||
end
|
||||
|
||||
context 'with unnormalized URL' do
|
||||
let(:url) { 'HTTP://EXAMPLE.com:80/foo%41%3A?bar=%41%3A#baz' }
|
||||
|
||||
before do
|
||||
stub_request(:get, 'http://example.com/foo%41%3A?bar=%41%3A')
|
||||
end
|
||||
|
||||
it 'normalizes scheme' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.scheme).to eq 'http'
|
||||
end
|
||||
end
|
||||
|
||||
it 'normalizes host' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.authority).to eq 'example.com'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does modify path' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.path).to eq '/foo%41%3A'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does modify query string' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.query).to eq 'bar=%41%3A'
|
||||
end
|
||||
end
|
||||
|
||||
it 'strips fragment' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.fragment).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-ASCII URL' do
|
||||
let(:url) { 'http://éxample.com/föo?bär=1' }
|
||||
|
||||
before do
|
||||
stub_request(:get, 'http://xn--xample-9ua.com/f%C3%B6o?b%C3%A4r=1')
|
||||
end
|
||||
|
||||
it 'IDN-encodes host' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.authority).to eq 'xn--xample-9ua.com'
|
||||
end
|
||||
end
|
||||
|
||||
it 'percent-escapes path and query string' do
|
||||
subject.perform
|
||||
|
||||
expect(a_request(:get, 'http://xn--xample-9ua.com/f%C3%B6o?b%C3%A4r=1')).to have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context 'with redirecting URL' do
|
||||
let(:url) { 'http://example.com/foo' }
|
||||
|
||||
before do
|
||||
stub_request(:get, 'http://example.com/foo').to_return(status: 302, headers: { 'Location' => 'HTTPS://EXAMPLE.net/Bar' })
|
||||
stub_request(:get, 'https://example.net/Bar').to_return(body: 'Lorem ipsum')
|
||||
end
|
||||
|
||||
it 'resolves redirect' do
|
||||
subject.perform do |response|
|
||||
expect(response.body.to_s).to eq 'Lorem ipsum'
|
||||
end
|
||||
|
||||
expect(a_request(:get, 'https://example.net/Bar')).to have_been_made
|
||||
end
|
||||
|
||||
it 'normalizes destination scheme' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.scheme).to eq 'https'
|
||||
end
|
||||
end
|
||||
|
||||
it 'normalizes destination host' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.authority).to eq 'example.net'
|
||||
end
|
||||
end
|
||||
|
||||
it 'does modify path' do
|
||||
subject.perform do |response|
|
||||
expect(response.request.uri.path).to eq '/Bar'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "response's body_with_limit method" do
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AccountAlias do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AccountDeletionRequest do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AccountModerationNote do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnouncementMute do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe AnnouncementReaction do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Announcement do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Backup do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ConversationMute do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CustomFilterKeyword do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CustomFilter do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Device do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe EncryptedMessage do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe FeaturedTag do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe FollowRecommendationSuppression do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ListAccount do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe List do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe LoginActivity do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Mute do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PreviewCard do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe PreviewCardTrend do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Relay do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe ScheduledStatus do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StatusStat do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe StatusTrend do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe SystemKey do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe TagFollow do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnavailableDomain do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UserInviteRequest do
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Web::Setting do
|
||||
end
|
|
@ -1,6 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
|
||||
# This needs to be defined before Rails is initialized
|
||||
RUN_SYSTEM_SPECS = ENV.fetch('RUN_SYSTEM_SPECS', false)
|
||||
|
||||
if RUN_SYSTEM_SPECS
|
||||
STREAMING_PORT = ENV.fetch('TEST_STREAMING_PORT', '4020')
|
||||
ENV['STREAMING_API_BASE_URL'] = "http://localhost:#{STREAMING_PORT}"
|
||||
end
|
||||
require File.expand_path('../config/environment', __dir__)
|
||||
|
||||
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
||||
|
@ -15,10 +23,14 @@ require 'chewy/rspec'
|
|||
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
WebMock.disable_net_connect!(allow: Chewy.settings[:host])
|
||||
WebMock.disable_net_connect!(allow: Chewy.settings[:host], allow_localhost: RUN_SYSTEM_SPECS)
|
||||
Sidekiq::Testing.inline!
|
||||
Sidekiq.logger = nil
|
||||
|
||||
# System tests config
|
||||
DatabaseCleaner.strategy = [:deletion]
|
||||
streaming_server_manager = StreamingServerManager.new
|
||||
|
||||
Devise::Test::ControllerHelpers.module_eval do
|
||||
alias_method :original_sign_in, :sign_in
|
||||
|
||||
|
@ -56,6 +68,8 @@ module SignedRequestHelpers
|
|||
end
|
||||
|
||||
RSpec.configure do |config|
|
||||
# This is set before running spec:system, see lib/tasks/tests.rake
|
||||
config.filter_run_excluding type: :system unless RUN_SYSTEM_SPECS
|
||||
config.fixture_path = Rails.root.join('spec', 'fixtures')
|
||||
config.use_transactional_fixtures = true
|
||||
config.order = 'random'
|
||||
|
@ -83,8 +97,7 @@ RSpec.configure do |config|
|
|||
end
|
||||
|
||||
config.before :each, type: :feature do
|
||||
https = ENV['LOCAL_HTTPS'] == 'true'
|
||||
Capybara.app_host = "http#{https ? 's' : ''}://#{ENV.fetch('LOCAL_DOMAIN')}"
|
||||
Capybara.current_driver = :rack_test
|
||||
end
|
||||
|
||||
config.before :each, type: :controller do
|
||||
|
@ -95,6 +108,35 @@ RSpec.configure do |config|
|
|||
stub_jsonld_contexts!
|
||||
end
|
||||
|
||||
config.before :suite do
|
||||
if RUN_SYSTEM_SPECS
|
||||
Webpacker.compile
|
||||
streaming_server_manager.start(port: STREAMING_PORT)
|
||||
end
|
||||
end
|
||||
|
||||
config.after :suite do
|
||||
streaming_server_manager.stop
|
||||
end
|
||||
|
||||
config.around :each, type: :system do |example|
|
||||
# driven_by :selenium, using: :chrome, screen_size: [1600, 1200]
|
||||
driven_by :selenium, using: :headless_chrome, screen_size: [1600, 1200]
|
||||
|
||||
# The streaming server needs access to the database
|
||||
# but with use_transactional_tests every transaction
|
||||
# is rolled-back, so the streaming server never sees the data
|
||||
# So we disable this feature for system tests, and use DatabaseCleaner to clean
|
||||
# the database tables between each test
|
||||
self.use_transactional_tests = false
|
||||
|
||||
DatabaseCleaner.cleaning do
|
||||
example.run
|
||||
end
|
||||
|
||||
self.use_transactional_tests = true
|
||||
end
|
||||
|
||||
config.before(:each) do |example|
|
||||
unless example.metadata[:paperclip_processing]
|
||||
allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance
|
||||
|
@ -105,6 +147,14 @@ RSpec.configure do |config|
|
|||
Rails.cache.clear
|
||||
redis.del(redis.keys)
|
||||
end
|
||||
|
||||
# Assign types based on dir name for non-inferred types
|
||||
config.define_derived_metadata(file_path: %r{/spec/}) do |metadata|
|
||||
unless metadata.key?(:type)
|
||||
match = metadata[:location].match(%r{/spec/([^/]+)/})
|
||||
metadata[:type] = match[1].singularize.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RSpec::Sidekiq.configure do |config|
|
||||
|
|
248
spec/requests/api/v2/filters/filters_spec.rb
Normal file
248
spec/requests/api/v2/filters/filters_spec.rb
Normal file
|
@ -0,0 +1,248 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Filters' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:scopes) { 'read:filters write:filters' }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
shared_examples 'unauthorized for invalid token' do
|
||||
let(:headers) { { 'Authorization' => '' } }
|
||||
|
||||
it 'returns http unauthorized' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(401)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/filters' do
|
||||
subject do
|
||||
get '/api/v2/filters', headers: headers
|
||||
end
|
||||
|
||||
let!(:filters) { Fabricate.times(3, :custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns the existing filters successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json.pluck(:id)).to match_array(filters.map { |filter| filter.id.to_s })
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v2/filters' do
|
||||
subject do
|
||||
post '/api/v2/filters', params: params, headers: headers
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
context 'with valid params' do
|
||||
let(:params) { { title: 'magic', context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns a filter with keywords', :aggregate_failures do
|
||||
subject
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json[:title]).to eq 'magic'
|
||||
expect(json[:filter_action]).to eq 'hide'
|
||||
expect(json[:context]).to eq ['home']
|
||||
expect(json[:keywords].map { |keyword| keyword.slice(:keyword, :whole_word) }).to eq [{ keyword: 'magic', whole_word: true }]
|
||||
end
|
||||
|
||||
it 'creates a filter', :aggregate_failures do
|
||||
subject
|
||||
|
||||
filter = user.account.custom_filters.first
|
||||
|
||||
expect(filter).to be_present
|
||||
expect(filter.keywords.pluck(:keyword)).to eq ['magic']
|
||||
expect(filter.context).to eq %w(home)
|
||||
expect(filter.irreversible?).to be true
|
||||
expect(filter.expires_at).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the required title param is missing' do
|
||||
let(:params) { { context: %w(home), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the required context param is missing' do
|
||||
let(:params) { { title: 'magic', filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the given context value is invalid' do
|
||||
let(:params) { { title: 'magic', context: %w(shaolin), filter_action: 'hide', keywords_attributes: [keyword: 'magic'] } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v2/filters/:id' do
|
||||
subject do
|
||||
get "/api/v2/filters/#{filter.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'write write:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns the filter successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(body_as_json[:id]).to eq(filter.id.to_s)
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT /api/v2/filters/:id' do
|
||||
subject do
|
||||
put "/api/v2/filters/#{filter.id}", params: params, headers: headers
|
||||
end
|
||||
|
||||
let!(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
let!(:keyword) { Fabricate(:custom_filter_keyword, custom_filter: filter) }
|
||||
let(:params) { {} }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
context 'when updating filter parameters' do
|
||||
context 'with valid params' do
|
||||
let(:params) { { title: 'updated', context: %w(home public) } }
|
||||
|
||||
it 'updates the filter successfully', :aggregate_failures do
|
||||
subject
|
||||
|
||||
filter.reload
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(filter.title).to eq 'updated'
|
||||
expect(filter.reload.context).to eq %w(home public)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:params) { { title: 'updated', context: %w(word) } }
|
||||
|
||||
it 'returns http unprocessable entity' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(422)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating keywords in bulk' do
|
||||
let(:params) { { keywords_attributes: [{ id: keyword.id, keyword: 'updated' }] } }
|
||||
|
||||
before do
|
||||
allow(redis).to receive_messages(publish: nil)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'updates the keyword' do
|
||||
subject
|
||||
|
||||
expect(keyword.reload.keyword).to eq 'updated'
|
||||
end
|
||||
|
||||
it 'sends exactly one filters_changed event' do
|
||||
subject
|
||||
|
||||
expect(redis).to have_received(:publish).with("timeline:#{user.account.id}", Oj.dump(event: :filters_changed)).once
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v2/filters/:id' do
|
||||
subject do
|
||||
delete "/api/v2/filters/#{filter.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:filter) { Fabricate(:custom_filter, account: user.account) }
|
||||
|
||||
it_behaves_like 'forbidden for wrong scope', 'read read:filters'
|
||||
it_behaves_like 'unauthorized for invalid token'
|
||||
|
||||
it 'returns http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'removes the filter' do
|
||||
subject
|
||||
|
||||
expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound
|
||||
end
|
||||
|
||||
context 'when the filter belongs to someone else' do
|
||||
let(:filter) { Fabricate(:custom_filter) }
|
||||
|
||||
it 'returns http not found' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
124
spec/requests/omniauth_callbacks_spec.rb
Normal file
124
spec/requests/omniauth_callbacks_spec.rb
Normal file
|
@ -0,0 +1,124 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'OmniAuth callbacks' do
|
||||
shared_examples 'omniauth provider callbacks' do |provider|
|
||||
subject { post send "user_#{provider}_omniauth_callback_path" }
|
||||
|
||||
context 'with full information in response' do
|
||||
before do
|
||||
mock_omniauth(provider, {
|
||||
provider: provider.to_s,
|
||||
uid: '123',
|
||||
info: {
|
||||
verified: 'true',
|
||||
email: 'user@host.example',
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
context 'without a matching user' do
|
||||
it 'creates a user and an identity and redirects to root path' do
|
||||
expect { subject }
|
||||
.to change(User, :count)
|
||||
.by(1)
|
||||
.and change(Identity, :count)
|
||||
.by(1)
|
||||
.and change(LoginActivity, :count)
|
||||
.by(1)
|
||||
|
||||
expect(User.last.email).to eq('user@host.example')
|
||||
expect(Identity.find_by(user: User.last).uid).to eq('123')
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a matching user and no matching identity' do
|
||||
before do
|
||||
Fabricate(:user, email: 'user@host.example')
|
||||
end
|
||||
|
||||
it 'matches the existing user, creates an identity, and redirects to root path' do
|
||||
expect { subject }
|
||||
.to not_change(User, :count)
|
||||
.and change(Identity, :count)
|
||||
.by(1)
|
||||
.and change(LoginActivity, :count)
|
||||
.by(1)
|
||||
|
||||
expect(Identity.find_by(user: User.last).uid).to eq('123')
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a matching user and a matching identity' do
|
||||
before do
|
||||
user = Fabricate(:user, email: 'user@host.example')
|
||||
Fabricate(:identity, user: user, uid: '123', provider: provider)
|
||||
end
|
||||
|
||||
it 'matches the existing records and redirects to root path' do
|
||||
expect { subject }
|
||||
.to not_change(User, :count)
|
||||
.and not_change(Identity, :count)
|
||||
.and change(LoginActivity, :count)
|
||||
.by(1)
|
||||
|
||||
expect(response).to redirect_to(root_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a response missing email address' do
|
||||
before do
|
||||
mock_omniauth(provider, {
|
||||
provider: provider.to_s,
|
||||
uid: '123',
|
||||
info: {
|
||||
verified: 'true',
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
it 'redirects to the auth setup page' do
|
||||
expect { subject }
|
||||
.to change(User, :count)
|
||||
.by(1)
|
||||
.and change(Identity, :count)
|
||||
.by(1)
|
||||
.and change(LoginActivity, :count)
|
||||
.by(1)
|
||||
|
||||
expect(response).to redirect_to(auth_setup_path(missing_email: '1'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user cannot be built' do
|
||||
before do
|
||||
allow(User).to receive(:find_for_oauth).and_return(User.new)
|
||||
end
|
||||
|
||||
it 'redirects to the new user signup page' do
|
||||
expect { subject }
|
||||
.to not_change(User, :count)
|
||||
.and not_change(Identity, :count)
|
||||
.and not_change(LoginActivity, :count)
|
||||
|
||||
expect(response).to redirect_to(new_user_registration_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#openid_connect', if: ENV['OIDC_ENABLED'] == 'true' && ENV['OIDC_SCOPE'].present? do
|
||||
include_examples 'omniauth provider callbacks', :openid_connect
|
||||
end
|
||||
|
||||
describe '#cas', if: ENV['CAS_ENABLED'] == 'true' do
|
||||
include_examples 'omniauth provider callbacks', :cas
|
||||
end
|
||||
|
||||
describe '#saml', if: ENV['SAML_ENABLED'] == 'true' do
|
||||
include_examples 'omniauth provider callbacks', :saml
|
||||
end
|
||||
end
|
|
@ -8,7 +8,17 @@ RSpec.describe ActivityPub::FetchRemoteKeyService, type: :service do
|
|||
let(:webfinger) { { subject: 'acct:alice@example.com', links: [{ rel: 'self', href: 'https://example.com/alice' }] } }
|
||||
|
||||
let(:public_key_pem) do
|
||||
"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39\n4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S\n6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh\n8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP\nahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW\npva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu\nHQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
<<~TEXT
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3L4vnpNLzVH31MeWI39
|
||||
4F0wKeJFsLDAsNXGeOu0QF2x+h1zLWZw/agqD2R3JPU9/kaDJGPIV2Sn5zLyUA9S
|
||||
6swCCMOtn7BBR9g9sucgXJmUFB0tACH2QSgHywMAybGfmSb3LsEMNKsGJ9VsvYoh
|
||||
8lDET6X4Pyw+ZJU0/OLo/41q9w+OrGtlsTm/PuPIeXnxa6BLqnDaxC+4IcjG/FiP
|
||||
ahNCTINl/1F/TgSSDZ4Taf4U9XFEIFw8wmgploELozzIzKq+t8nhQYkgAkt64euW
|
||||
pva3qL5KD1mTIZQEP+LZvh3s2WHrLi3fhbdRuwQ2c0KkJA2oSTFPDpqqbPGZ3Qvu
|
||||
HQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
TEXT
|
||||
end
|
||||
|
||||
let(:public_key_id) { 'https://example.com/alice#main-key' }
|
||||
|
|
|
@ -100,8 +100,18 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
|
|||
username: 'bob',
|
||||
domain: 'example.com',
|
||||
uri: 'https://example.com/users/bob',
|
||||
public_key: "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId\nW3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7\nCmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m\nCCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua\n4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG\nTvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD\nMwIDAQAB\n-----END PUBLIC KEY-----\n",
|
||||
private_key: nil)
|
||||
private_key: nil,
|
||||
public_key: <<~TEXT)
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuuYyoyfsRkYnXRotMsId
|
||||
W3euBDDfiv9oVqOxUVC7bhel8KednIMrMCRWFAkgJhbrlzbIkjVr68o1MP9qLcn7
|
||||
CmH/BXHp7yhuFTr4byjdJKpwB+/i2jNEsvDH5jR8WTAeTCe0x/QHg21V3F7dSI5m
|
||||
CCZ/1dSIyOXLRTWVlfDlm3rE4ntlCo+US3/7oSWbg/4/4qEnt1HC32kvklgScxua
|
||||
4LR5ATdoXa5bFoopPWhul7MJ6NyWCyQyScUuGdlj8EN4kmKQJvphKHrI9fvhgOuG
|
||||
TvhTR1S5InA4azSSchY0tXEEw/VNxraeX0KPjbgr6DPcwhPd/m0nhVDq0zVyVBBD
|
||||
MwIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
TEXT
|
||||
end
|
||||
|
||||
let(:payload) do
|
||||
|
@ -125,7 +135,14 @@ RSpec.describe ActivityPub::ProcessCollectionService, type: :service do
|
|||
type: 'RsaSignature2017',
|
||||
creator: 'https://example.com/users/bob#main-key',
|
||||
created: '2022-03-09T21:57:25Z',
|
||||
signatureValue: 'WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UVPZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3pCxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWiQ/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gkfsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3PojCSWv6mNTmRGoLZzOscCAYQA6cKw==',
|
||||
signatureValue: 'WculK0LelTQ0MvGwU9TPoq5pFzFfGYRDCJqjZ232/Udj4' \
|
||||
'CHqDTGOSw5UTDLShqBOyycCkbZGrQwXG+dpyDpQLSe1UV' \
|
||||
'PZ5TPQtc/9XtI57WlS2nMNpdvRuxGnnb2btPdesXZ7n3p' \
|
||||
'Cxo0zjaXrJMe0mqQh5QJO22mahb4bDwwmfTHgbD3nmkD+' \
|
||||
'fBfGi+UV2qWwqr+jlV4L4JqNkh0gWljF5KTePLRRZCuWi' \
|
||||
'Q/FAt7c67636cdIPf7fR+usjuZltTQyLZKEGuK8VUn2Gk' \
|
||||
'fsx5qns7Vcjvlz1JqlAjyO8HPBbzTTHzUG2nUOIgC3Poj' \
|
||||
'CSWv6mNTmRGoLZzOscCAYQA6cKw==',
|
||||
},
|
||||
'@id': 'https://example.com/users/bob/statuses/107928807471117876/activity',
|
||||
'@type': 'https://www.w3.org/ns/activitystreams#Create',
|
||||
|
|
|
@ -5,105 +5,249 @@ require 'rails_helper'
|
|||
RSpec.describe FetchLinkCardService, type: :service do
|
||||
subject { described_class.new }
|
||||
|
||||
let(:html) { '<!doctype html><title>Hello world</title>' }
|
||||
let(:oembed_cache) { nil }
|
||||
|
||||
before do
|
||||
stub_request(:get, 'http://example.xn--fiqs8s/').to_return(request_fixture('idn.txt'))
|
||||
stub_request(:get, 'http://example.com/html').to_return(headers: { 'Content-Type' => 'text/html' }, body: html)
|
||||
stub_request(:get, 'http://example.com/not-found').to_return(status: 404, headers: { 'Content-Type' => 'text/html' }, body: html)
|
||||
stub_request(:get, 'http://example.com/text').to_return(status: 404, headers: { 'Content-Type' => 'text/plain' }, body: 'Hello')
|
||||
stub_request(:get, 'http://example.com/redirect').to_return(status: 302, headers: { 'Location' => 'http://example.com/html' })
|
||||
stub_request(:get, 'http://example.com/redirect-to-404').to_return(status: 302, headers: { 'Location' => 'http://example.com/not-found' })
|
||||
stub_request(:get, 'http://example.com/oembed?url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }')
|
||||
stub_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html').to_return(headers: { 'Content-Type' => 'application/json' }, body: '{ "version": "1.0", "type": "link", "title": "oEmbed title" }')
|
||||
|
||||
stub_request(:get, 'http://example.xn--fiqs8s')
|
||||
stub_request(:get, 'http://example.com/日本語')
|
||||
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1')
|
||||
stub_request(:get, 'http://example.com/test-')
|
||||
|
||||
stub_request(:get, 'http://example.com/sjis').to_return(request_fixture('sjis.txt'))
|
||||
stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt'))
|
||||
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
|
||||
stub_request(:get, 'http://example.com/日本語').to_return(request_fixture('sjis.txt'))
|
||||
stub_request(:get, 'https://github.com/qbi/WannaCry').to_return(status: 404)
|
||||
stub_request(:get, 'http://example.com/test?data=file.gpx%5E1').to_return(status: 200)
|
||||
stub_request(:get, 'http://example.com/test-').to_return(request_fixture('idn.txt'))
|
||||
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
|
||||
|
||||
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
|
||||
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
context 'with a local status' do
|
||||
context 'with an IDN url' do
|
||||
context 'with URL of a regular HTML page' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
|
||||
|
||||
it 'creates preview card' do
|
||||
expect(status.preview_card).to_not be_nil
|
||||
expect(status.preview_card.url).to eq 'http://example.com/html'
|
||||
expect(status.preview_card.title).to eq 'Hello world'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with URL of a page with no title' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
|
||||
let(:html) { '<!doctype html><title></title>' }
|
||||
|
||||
it 'does not create a preview card' do
|
||||
expect(status.preview_card).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a URL of a plain-text page' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/text') }
|
||||
|
||||
it 'does not create a preview card' do
|
||||
expect(status.preview_card).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with multiple URLs' do
|
||||
let(:status) { Fabricate(:status, text: 'ftp://example.com http://example.com/html http://example.com/text') }
|
||||
|
||||
it 'fetches the first valid URL' do
|
||||
expect(a_request(:get, 'http://example.com/html')).to have_been_made
|
||||
end
|
||||
|
||||
it 'does not fetch the second valid URL' do
|
||||
expect(a_request(:get, 'http://example.com/text/')).to_not have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a redirect URL' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/redirect') }
|
||||
|
||||
it 'follows redirect' do
|
||||
expect(a_request(:get, 'http://example.com/redirect')).to have_been_made.once
|
||||
expect(a_request(:get, 'http://example.com/html')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'creates preview card' do
|
||||
expect(status.preview_card).to_not be_nil
|
||||
expect(status.preview_card.url).to eq 'http://example.com/html'
|
||||
expect(status.preview_card.title).to eq 'Hello world'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a broken redirect URL' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') }
|
||||
|
||||
it 'follows redirect' do
|
||||
expect(a_request(:get, 'http://example.com/redirect-to-404')).to have_been_made.once
|
||||
expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'does not create a preview card' do
|
||||
expect(status.preview_card).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a 404 URL' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/not-found') }
|
||||
|
||||
it 'does not create a preview card' do
|
||||
expect(status.preview_card).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an IDN URL' do
|
||||
let(:status) { Fabricate(:status, text: 'Check out http://example.中国') }
|
||||
|
||||
it 'works with IDN URLs' do
|
||||
expect(a_request(:get, 'http://example.xn--fiqs8s/')).to have_been_made.at_least_once
|
||||
it 'fetches the URL' do
|
||||
expect(a_request(:get, 'http://example.xn--fiqs8s/')).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an SJIS url' do
|
||||
context 'with a URL of a page in Shift JIS encoding' do
|
||||
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis') }
|
||||
|
||||
it 'works with SJIS' do
|
||||
expect(a_request(:get, 'http://example.com/sjis')).to have_been_made.at_least_once
|
||||
it 'decodes the HTML' do
|
||||
expect(status.preview_cards.first.title).to eq('SJISのページ')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid SJIS url' do
|
||||
context 'with a URL of a page in Shift JIS encoding labeled as UTF-8' do
|
||||
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis_with_wrong_charset') }
|
||||
|
||||
it 'works with SJIS even with wrong charset header' do
|
||||
expect(a_request(:get, 'http://example.com/sjis_with_wrong_charset')).to have_been_made.at_least_once
|
||||
it 'decodes the HTML despite the wrong charset header' do
|
||||
expect(status.preview_cards.first.title).to eq('SJISのページ')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an koi8-r url' do
|
||||
context 'with a URL of a page in KOI8-R encoding' do
|
||||
let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') }
|
||||
|
||||
it 'works with koi8-r' do
|
||||
expect(a_request(:get, 'http://example.com/koi8-r')).to have_been_made.at_least_once
|
||||
it 'decodes the HTML' do
|
||||
expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a windows-1251 url' do
|
||||
context 'with a URL of a page in Windows-1251 encoding' do
|
||||
let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') }
|
||||
|
||||
it 'works with windows-1251' do
|
||||
expect(a_request(:get, 'http://example.com/windows-1251')).to have_been_made.at_least_once
|
||||
it 'decodes the HTML' do
|
||||
expect(status.preview_cards.first.title).to eq('сэмпл текст')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a japanese path url' do
|
||||
context 'with a Japanese path URL' do
|
||||
let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') }
|
||||
|
||||
it 'works with Japanese path string' do
|
||||
expect(a_request(:get, 'http://example.com/日本語')).to have_been_made.at_least_once
|
||||
expect(status.preview_cards.first.title).to eq('SJISのページ')
|
||||
it 'fetches the URL' do
|
||||
expect(a_request(:get, 'http://example.com/日本語')).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a hyphen-suffixed url' do
|
||||
context 'with a hyphen-suffixed URL' do
|
||||
let(:status) { Fabricate(:status, text: 'test http://example.com/test-') }
|
||||
|
||||
it 'works with a URL ending with a hyphen' do
|
||||
expect(a_request(:get, 'http://example.com/test-')).to have_been_made.at_least_once
|
||||
it 'fetches the URL' do
|
||||
expect(a_request(:get, 'http://example.com/test-')).to have_been_made.once
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an isolated url' do
|
||||
context 'with a caret-suffixed URL' do
|
||||
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
|
||||
|
||||
it 'fetches the URL' do
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'does not strip the caret before fetching' do
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-isolated URL' do
|
||||
let(:status) { Fabricate(:status, text: 'testhttp://example.com/sjis') }
|
||||
|
||||
it 'does not fetch URLs with not isolated from their surroundings' do
|
||||
it 'does not fetch URLs not isolated from their surroundings' do
|
||||
expect(a_request(:get, 'http://example.com/sjis')).to_not have_been_made
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a url that has a caret' do
|
||||
let(:status) { Fabricate(:status, text: 'test http://example.com/test?data=file.gpx^1') }
|
||||
context 'with a URL of a page with oEmbed support' do
|
||||
let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' }
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
|
||||
|
||||
it 'does fetch URLs with a caret in search params' do
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx')).to_not have_been_made
|
||||
expect(a_request(:get, 'http://example.com/test?data=file.gpx%5E1')).to have_been_made.once
|
||||
it 'fetches the oEmbed URL' do
|
||||
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'creates preview card' do
|
||||
expect(status.preview_card).to_not be_nil
|
||||
expect(status.preview_card.url).to eq 'http://example.com/html'
|
||||
expect(status.preview_card.title).to eq 'oEmbed title'
|
||||
end
|
||||
|
||||
context 'when oEmbed endpoint cache populated' do
|
||||
let(:oembed_cache) { { endpoint: 'http://example.com/oembed?format=json&url={url}', format: :json } }
|
||||
|
||||
it 'uses the cached oEmbed response' do
|
||||
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to_not have_been_made
|
||||
expect(a_request(:get, 'http://example.com/oembed?format=json&url=http://example.com/html')).to have_been_made
|
||||
end
|
||||
|
||||
it 'creates preview card' do
|
||||
expect(status.preview_card).to_not be_nil
|
||||
expect(status.preview_card.url).to eq 'http://example.com/html'
|
||||
expect(status.preview_card.title).to eq 'oEmbed title'
|
||||
end
|
||||
end
|
||||
|
||||
# If the original HTML URL for whatever reason (e.g. DOS protection) redirects to
|
||||
# an error page, we can still use the cached oEmbed but should not use the
|
||||
# redirect URL on the card.
|
||||
context 'when oEmbed endpoint cache populated but page returns 404' do
|
||||
let(:status) { Fabricate(:status, text: 'http://example.com/redirect-to-404') }
|
||||
let(:oembed_cache) { { endpoint: 'http://example.com/oembed?url=http://example.com/html', format: :json } }
|
||||
|
||||
it 'uses the cached oEmbed response' do
|
||||
expect(a_request(:get, 'http://example.com/oembed?url=http://example.com/html')).to have_been_made
|
||||
end
|
||||
|
||||
it 'creates preview card' do
|
||||
expect(status.preview_card).to_not be_nil
|
||||
expect(status.preview_card.title).to eq 'oEmbed title'
|
||||
end
|
||||
|
||||
it 'uses the original URL' do
|
||||
expect(status.preview_card&.url).to eq 'http://example.com/redirect-to-404'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote status' do
|
||||
let(:status) { Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: 'Habt ihr ein paar gute Links zu <a>foo</a> #<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen? Ich will mal unter <br> <a href="https://github.com/qbi/WannaCry" target="_blank" rel="noopener noreferrer" title="https://github.com/qbi/WannaCry">https://github.com/qbi/WannaCry</a> was sammeln. !<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener noreferrer" title="http://sn.jonkman.ca/group/416/id">security</a> ') }
|
||||
let(:status) do
|
||||
Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: <<-TEXT)
|
||||
Habt ihr ein paar gute Links zu <a>foo</a>
|
||||
#<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen?
|
||||
Ich will mal unter <br> <a href="http://example.com/not-found" target="_blank" rel="noopener noreferrer" title="http://example.com/not-found">http://example.com/not-found</a> was sammeln. !
|
||||
<a href="http://sn.jonkman.ca/group/416/id" target="_blank" rel="noopener noreferrer" title="http://sn.jonkman.ca/group/416/id">security</a>
|
||||
TEXT
|
||||
end
|
||||
|
||||
it 'parses out URLs' do
|
||||
expect(a_request(:get, 'https://github.com/qbi/WannaCry')).to have_been_made.at_least_once
|
||||
expect(a_request(:get, 'http://example.com/not-found')).to have_been_made.once
|
||||
end
|
||||
|
||||
it 'ignores URLs to hashtags' do
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe UnmuteService, type: :service do
|
||||
subject { described_class.new }
|
||||
end
|
|
@ -52,3 +52,80 @@ def expect_push_bulk_to_match(klass, matcher)
|
|||
'args' => matcher,
|
||||
}))
|
||||
end
|
||||
|
||||
class StreamingServerManager
|
||||
@running_thread = nil
|
||||
|
||||
def initialize
|
||||
at_exit { stop }
|
||||
end
|
||||
|
||||
def start(port: 4020)
|
||||
return if @running_thread
|
||||
|
||||
queue = Queue.new
|
||||
|
||||
@queue = queue
|
||||
|
||||
@running_thread = Thread.new do
|
||||
Open3.popen2e(
|
||||
{
|
||||
'REDIS_NAMESPACE' => ENV.fetch('REDIS_NAMESPACE'),
|
||||
'DB_NAME' => "#{ENV.fetch('DB_NAME', 'mastodon')}_test#{ENV.fetch('TEST_ENV_NUMBER', '')}",
|
||||
'RAILS_ENV' => ENV.fetch('RAILS_ENV', 'test'),
|
||||
'NODE_ENV' => ENV.fetch('STREAMING_NODE_ENV', 'development'),
|
||||
'PORT' => port.to_s,
|
||||
},
|
||||
'node index.js', # must not call yarn here, otherwise it will fail because yarn does not send signals to its child process
|
||||
chdir: Rails.root.join('streaming')
|
||||
) do |_stdin, stdout_err, process_thread|
|
||||
status = :starting
|
||||
|
||||
# Spawn a thread to listen on streaming server output
|
||||
output_thread = Thread.new do
|
||||
stdout_err.each_line do |line|
|
||||
Rails.logger.info "Streaming server: #{line}"
|
||||
|
||||
if status == :starting && line.match('Streaming API now listening on')
|
||||
status = :started
|
||||
@queue.enq 'started'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# And another thread to listen on commands from the main thread
|
||||
loop do
|
||||
msg = queue.pop
|
||||
|
||||
case msg
|
||||
when 'stop'
|
||||
# we need to properly stop the reading thread
|
||||
output_thread.kill
|
||||
|
||||
# Then stop the node process
|
||||
Process.kill('KILL', process_thread.pid)
|
||||
|
||||
# And we stop ourselves
|
||||
@running_thread.kill
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# wait for 10 seconds for the streaming server to start
|
||||
Timeout.timeout(10) do
|
||||
loop do
|
||||
break if @queue.pop == 'started'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stop
|
||||
return unless @running_thread
|
||||
|
||||
@queue.enq 'stop'
|
||||
|
||||
# Wait for the thread to end
|
||||
@running_thread.join
|
||||
end
|
||||
end
|
||||
|
|
7
spec/support/omniauth_mocks.rb
Normal file
7
spec/support/omniauth_mocks.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
OmniAuth.config.test_mode = true
|
||||
|
||||
def mock_omniauth(provider, data)
|
||||
OmniAuth.config.mock_auth[provider] = OmniAuth::AuthHash.new(data)
|
||||
end
|
|
@ -9,6 +9,8 @@ module ProfileStories
|
|||
email: email, password: password, confirmed_at: confirmed_at,
|
||||
account: Fabricate(:account, username: 'bob')
|
||||
)
|
||||
|
||||
Web::Setting.where(user: bob).first_or_initialize(user: bob).update!(data: { introductionVersion: 201812160442020 }) if finished_onboarding # rubocop:disable Style/NumericLiterals
|
||||
end
|
||||
|
||||
def as_a_logged_in_user
|
||||
|
@ -42,4 +44,8 @@ module ProfileStories
|
|||
def password
|
||||
@password ||= 'password'
|
||||
end
|
||||
|
||||
def finished_onboarding
|
||||
@finished_onboarding || false
|
||||
end
|
||||
end
|
||||
|
|
45
spec/system/new_statuses_spec.rb
Normal file
45
spec/system/new_statuses_spec.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'NewStatuses' do
|
||||
include ProfileStories
|
||||
|
||||
subject { page }
|
||||
|
||||
let(:email) { 'test@example.com' }
|
||||
let(:password) { 'password' }
|
||||
let(:confirmed_at) { Time.zone.now }
|
||||
let(:finished_onboarding) { true }
|
||||
|
||||
before do
|
||||
as_a_logged_in_user
|
||||
visit root_path
|
||||
end
|
||||
|
||||
it 'can be posted' do
|
||||
expect(subject).to have_css('div.app-holder')
|
||||
|
||||
status_text = 'This is a new status!'
|
||||
|
||||
within('.compose-form') do
|
||||
fill_in "What's on your mind?", with: status_text
|
||||
click_on 'Publish!'
|
||||
end
|
||||
|
||||
expect(subject).to have_selector('.status__content__text', text: status_text)
|
||||
end
|
||||
|
||||
it 'can be posted again' do
|
||||
expect(subject).to have_css('div.app-holder')
|
||||
|
||||
status_text = 'This is a second status!'
|
||||
|
||||
within('.compose-form') do
|
||||
fill_in "What's on your mind?", with: status_text
|
||||
click_on 'Publish!'
|
||||
end
|
||||
|
||||
expect(subject).to have_selector('.status__content__text', text: status_text)
|
||||
end
|
||||
end
|
60
spec/validators/language_validator_spec.rb
Normal file
60
spec/validators/language_validator_spec.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe LanguageValidator do
|
||||
let(:record_class) do
|
||||
Class.new do
|
||||
include ActiveModel::Validations
|
||||
attr_accessor :locale
|
||||
|
||||
validates :locale, language: true
|
||||
end
|
||||
end
|
||||
let(:record) { record_class.new }
|
||||
|
||||
describe '#validate_each' do
|
||||
context 'with a nil value' do
|
||||
it 'does not add errors' do
|
||||
record.locale = nil
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an array of values' do
|
||||
it 'does not add errors with array of existing locales' do
|
||||
record.locale = %w(en fr)
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
|
||||
it 'adds errors with array having some non-existing locales' do
|
||||
record.locale = %w(en fr missing)
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:locale)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a locale string' do
|
||||
it 'does not add errors when string is an existing locale' do
|
||||
record.locale = 'en'
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
|
||||
it 'adds errors when string is non-existing locale' do
|
||||
record.locale = 'missing'
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:locale)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,32 +2,64 @@
|
|||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe URLValidator, type: :validator do
|
||||
describe '#validate_each' do
|
||||
before do
|
||||
allow(validator).to receive(:compliant?).with(value) { compliant }
|
||||
validator.validate_each(record, attribute, value)
|
||||
describe URLValidator do
|
||||
let(:record_class) do
|
||||
Class.new do
|
||||
include ActiveModel::Validations
|
||||
attr_accessor :profile
|
||||
|
||||
validates :profile, url: true
|
||||
end
|
||||
end
|
||||
let(:record) { record_class.new }
|
||||
|
||||
let(:validator) { described_class.new(attributes: [attribute]) }
|
||||
let(:record) { instance_double(Webhook, errors: errors) }
|
||||
let(:errors) { instance_double(ActiveModel::Errors, add: nil) }
|
||||
let(:value) { '' }
|
||||
let(:attribute) { :foo }
|
||||
describe '#validate_each' do
|
||||
context 'with a nil value' do
|
||||
it 'adds errors' do
|
||||
record.profile = nil
|
||||
|
||||
context 'when not compliant?' do
|
||||
let(:compliant) { false }
|
||||
|
||||
it 'calls errors.add' do
|
||||
expect(errors).to have_received(:add).with(attribute, :invalid)
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:profile)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when compliant?' do
|
||||
let(:compliant) { true }
|
||||
context 'with an invalid url scheme' do
|
||||
it 'adds errors' do
|
||||
record.profile = 'ftp://example.com/page'
|
||||
|
||||
it 'not calls errors.add' do
|
||||
expect(errors).to_not have_received(:add).with(attribute, any_args)
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:profile)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a hostname' do
|
||||
it 'adds errors' do
|
||||
record.profile = 'https:///page'
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:profile)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an unparseable value' do
|
||||
it 'adds errors' do
|
||||
record.profile = 'https://host:port/page' # non-numeric port string causes invalid uri error
|
||||
|
||||
expect(record).to_not be_valid
|
||||
expect(record.errors.first.attribute).to eq(:profile)
|
||||
expect(record.errors.first.type).to eq(:invalid)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a valid url' do
|
||||
it 'does not add errors' do
|
||||
record.profile = 'https://example.com/page'
|
||||
|
||||
expect(record).to be_valid
|
||||
expect(record.errors).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue