Merge commit '23e32a4b3031d1da8b911e0145d61b4dd47c4f96' into glitch-soc/merge-upstream
Conflicts: - `Gemfile.lock`: Conflict because we had updated the `json-ld` gem to fix a yanked dependency. Kept our version of `json-ld` while updating other dependencies.
This commit is contained in:
commit
bc33be0342
45 changed files with 791 additions and 1063 deletions
|
@ -57,7 +57,6 @@ RSpec/AnyInstance:
|
||||||
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
||||||
- 'spec/controllers/admin/accounts_controller_spec.rb'
|
- 'spec/controllers/admin/accounts_controller_spec.rb'
|
||||||
- 'spec/controllers/admin/resets_controller_spec.rb'
|
- 'spec/controllers/admin/resets_controller_spec.rb'
|
||||||
- 'spec/controllers/admin/settings/branding_controller_spec.rb'
|
|
||||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
||||||
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
- 'spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb'
|
||||||
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
|
- 'spec/controllers/settings/two_factor_authentication/recovery_codes_controller_spec.rb'
|
||||||
|
|
4
Gemfile
4
Gemfile
|
@ -88,7 +88,7 @@ gem 'simple-navigation', '~> 4.4'
|
||||||
gem 'simple_form', '~> 5.2'
|
gem 'simple_form', '~> 5.2'
|
||||||
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.4', require: 'sprockets/railtie'
|
||||||
gem 'stoplight', '~> 3.0.1'
|
gem 'stoplight', '~> 3.0.1'
|
||||||
gem 'strong_migrations', '1.3.0'
|
gem 'strong_migrations', '1.6.4'
|
||||||
gem 'tty-prompt', '~> 0.23', require: false
|
gem 'tty-prompt', '~> 0.23', require: false
|
||||||
gem 'twitter-text', '~> 3.1.0'
|
gem 'twitter-text', '~> 3.1.0'
|
||||||
gem 'tzinfo-data', '~> 1.2023'
|
gem 'tzinfo-data', '~> 1.2023'
|
||||||
|
@ -195,7 +195,7 @@ gem 'xorcist', '~> 1.1'
|
||||||
|
|
||||||
gem 'cocoon', '~> 1.2'
|
gem 'cocoon', '~> 1.2'
|
||||||
|
|
||||||
gem 'net-http', '~> 0.3.2'
|
gem 'net-http', '~> 0.4.0'
|
||||||
gem 'rubyzip', '~> 2.3'
|
gem 'rubyzip', '~> 2.3'
|
||||||
|
|
||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -130,8 +130,8 @@ GEM
|
||||||
attr_required (1.0.1)
|
attr_required (1.0.1)
|
||||||
awrence (1.2.1)
|
awrence (1.2.1)
|
||||||
aws-eventstream (1.2.0)
|
aws-eventstream (1.2.0)
|
||||||
aws-partitions (1.809.0)
|
aws-partitions (1.828.0)
|
||||||
aws-sdk-core (3.181.0)
|
aws-sdk-core (3.183.1)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.5)
|
aws-sigv4 (~> 1.5)
|
||||||
|
@ -139,7 +139,7 @@ GEM
|
||||||
aws-sdk-kms (1.71.0)
|
aws-sdk-kms (1.71.0)
|
||||||
aws-sdk-core (~> 3, >= 3.177.0)
|
aws-sdk-core (~> 3, >= 3.177.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.133.0)
|
aws-sdk-s3 (1.136.0)
|
||||||
aws-sdk-core (~> 3, >= 3.181.0)
|
aws-sdk-core (~> 3, >= 3.181.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.6)
|
aws-sigv4 (~> 1.6)
|
||||||
|
@ -389,10 +389,10 @@ GEM
|
||||||
multi_json (~> 1.15)
|
multi_json (~> 1.15)
|
||||||
rack (>= 2.2, < 4)
|
rack (>= 2.2, < 4)
|
||||||
rdf (~> 3.3)
|
rdf (~> 3.3)
|
||||||
json-ld-preloaded (3.2.2)
|
json-ld-preloaded (3.3.0)
|
||||||
json-ld (~> 3.2)
|
json-ld (~> 3.3)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.3)
|
||||||
json-schema (4.0.0)
|
json-schema (4.1.1)
|
||||||
addressable (>= 2.8)
|
addressable (>= 2.8)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
jwt (2.7.1)
|
jwt (2.7.1)
|
||||||
|
@ -454,13 +454,13 @@ GEM
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.0808)
|
mime-types-data (3.2023.0808)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.4)
|
mini_portile2 (2.8.5)
|
||||||
minitest (5.20.0)
|
minitest (5.20.0)
|
||||||
msgpack (1.7.2)
|
msgpack (1.7.2)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multipart-post (2.3.0)
|
multipart-post (2.3.0)
|
||||||
mutex_m (0.1.2)
|
mutex_m (0.1.2)
|
||||||
net-http (0.3.2)
|
net-http (0.4.0)
|
||||||
uri
|
uri
|
||||||
net-http-persistent (4.0.2)
|
net-http-persistent (4.0.2)
|
||||||
connection_pool (~> 2.2)
|
connection_pool (~> 2.2)
|
||||||
|
@ -689,13 +689,13 @@ GEM
|
||||||
fugit (~> 1.1, >= 1.1.6)
|
fugit (~> 1.1, >= 1.1.6)
|
||||||
safety_net_attestation (0.4.0)
|
safety_net_attestation (0.4.0)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
sanitize (6.0.2)
|
sanitize (6.1.0)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
scenic (1.7.0)
|
scenic (1.7.0)
|
||||||
activerecord (>= 4.0.0)
|
activerecord (>= 4.0.0)
|
||||||
railties (>= 4.0.0)
|
railties (>= 4.0.0)
|
||||||
selenium-webdriver (4.13.1)
|
selenium-webdriver (4.15.0)
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
|
@ -710,7 +710,7 @@ GEM
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 6, < 8)
|
sidekiq (>= 6, < 8)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (7.1.29)
|
sidekiq-unique-jobs (7.1.30)
|
||||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
redis (< 5.0)
|
redis (< 5.0)
|
||||||
|
@ -718,7 +718,7 @@ GEM
|
||||||
thor (>= 0.20, < 3.0)
|
thor (>= 0.20, < 3.0)
|
||||||
simple-navigation (4.4.0)
|
simple-navigation (4.4.0)
|
||||||
activesupport (>= 2.3.2)
|
activesupport (>= 2.3.2)
|
||||||
simple_form (5.2.0)
|
simple_form (5.3.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
activemodel (>= 5.2)
|
activemodel (>= 5.2)
|
||||||
simplecov (0.22.0)
|
simplecov (0.22.0)
|
||||||
|
@ -740,7 +740,7 @@ GEM
|
||||||
stoplight (3.0.2)
|
stoplight (3.0.2)
|
||||||
redlock (~> 1.0)
|
redlock (~> 1.0)
|
||||||
stringio (3.0.8)
|
stringio (3.0.8)
|
||||||
strong_migrations (1.3.0)
|
strong_migrations (1.6.4)
|
||||||
activerecord (>= 5.2)
|
activerecord (>= 5.2)
|
||||||
swd (1.3.0)
|
swd (1.3.0)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
|
@ -753,7 +753,7 @@ GEM
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
test-prof (1.2.3)
|
test-prof (1.2.3)
|
||||||
thor (1.2.2)
|
thor (1.3.0)
|
||||||
tilt (2.3.0)
|
tilt (2.3.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.0)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
|
@ -883,7 +883,7 @@ DEPENDENCIES
|
||||||
md-paperclip-azure (~> 2.2)
|
md-paperclip-azure (~> 2.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
mime-types (~> 3.5.0)
|
mime-types (~> 3.5.0)
|
||||||
net-http (~> 0.3.2)
|
net-http (~> 0.4.0)
|
||||||
net-ldap (~> 0.18)
|
net-ldap (~> 0.18)
|
||||||
nokogiri (~> 1.15)
|
nokogiri (~> 1.15)
|
||||||
nsa!
|
nsa!
|
||||||
|
@ -941,7 +941,7 @@ DEPENDENCIES
|
||||||
sprockets-rails (~> 3.4)
|
sprockets-rails (~> 3.4)
|
||||||
stackprof
|
stackprof
|
||||||
stoplight (~> 3.0.1)
|
stoplight (~> 3.0.1)
|
||||||
strong_migrations (= 1.3.0)
|
strong_migrations (= 1.6.4)
|
||||||
test-prof
|
test-prof
|
||||||
thor (~> 1.2)
|
thor (~> 1.2)
|
||||||
tty-prompt (~> 0.23)
|
tty-prompt (~> 0.23)
|
||||||
|
|
|
@ -6,7 +6,7 @@ module Admin::AccountModerationNotesHelper
|
||||||
|
|
||||||
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||||
safe_join([
|
safe_join([
|
||||||
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
|
image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'),
|
||||||
content_tag(:span, account.acct, class: 'username'),
|
content_tag(:span, account.acct, class: 'username'),
|
||||||
], ' ')
|
], ' ')
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ module SettingsHelper
|
||||||
return if account.nil?
|
return if account.nil?
|
||||||
|
|
||||||
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
|
link_to ActivityPub::TagManager.instance.url_for(account), class: 'name-tag', title: account.acct do
|
||||||
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
safe_join([image_tag(account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar'), content_tag(:span, account.acct, class: 'username')], ' ')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ exports[`<Avatar /> Autoplay renders a animated avatar 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="alice"
|
alt=""
|
||||||
src="/animated/alice.gif"
|
src="/animated/alice.gif"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,7 +32,7 @@ exports[`<Avatar /> Still renders a still avatar 1`] = `
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
alt="alice"
|
alt=""
|
||||||
src="/static/alice.jpg"
|
src="/static/alice.jpg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const Avatar: React.FC<Props> = ({
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{src && <img src={src} alt={account?.get('acct')} />}
|
{src && <img src={src} alt='' />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
80
app/javascript/mastodon/features/__tests__/toggle-play.jsx
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import { render, fireEvent } from '@testing-library/react';
|
||||||
|
|
||||||
|
class Media extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
paused: props.paused || false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMediaClick = () => {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
paused: !prevState.paused,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (typeof onClick === 'function') {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title } = this.props;
|
||||||
|
const mediaElements = document.querySelectorAll(`div[title="${title}"]`);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
mediaElements.forEach(element => {
|
||||||
|
if (element !== this && !element.classList.contains('paused')) {
|
||||||
|
element.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { title } = this.props;
|
||||||
|
const { paused } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button title={title} onClick={this.handleMediaClick}>
|
||||||
|
Media Component - {paused ? 'Paused' : 'Playing'}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Media.propTypes = {
|
||||||
|
title: PropTypes.string.isRequired,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
paused: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Media attachments test', () => {
|
||||||
|
let currentMedia = null;
|
||||||
|
const togglePlayMock = jest.fn();
|
||||||
|
|
||||||
|
it('plays a new media file and pauses others that were playing', () => {
|
||||||
|
const container = render(
|
||||||
|
<div>
|
||||||
|
<Media title='firstMedia' paused onClick={togglePlayMock} />
|
||||||
|
<Media title='secondMedia' paused onClick={togglePlayMock} />
|
||||||
|
</div>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('firstMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(1);
|
||||||
|
currentMedia = container.getByTitle('firstMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
|
||||||
|
fireEvent.click(container.getByTitle('secondMedia'));
|
||||||
|
expect(togglePlayMock).toHaveBeenCalledTimes(2);
|
||||||
|
currentMedia = container.getByTitle('secondMedia');
|
||||||
|
expect(currentMedia.textContent).toMatch(/Playing/);
|
||||||
|
});
|
||||||
|
});
|
|
@ -178,7 +178,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
modalType: 'IMAGE',
|
modalType: 'IMAGE',
|
||||||
modalProps: {
|
modalProps: {
|
||||||
src: account.get('avatar'),
|
src: account.get('avatar'),
|
||||||
alt: account.get('acct'),
|
alt: '',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { formatTime, getPointerPosition, fileNameFromURL } from 'mastodon/featur
|
||||||
|
|
||||||
import { Blurhash } from '../../components/blurhash';
|
import { Blurhash } from '../../components/blurhash';
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
|
|
||||||
import Visualizer from './visualizer';
|
import Visualizer from './visualizer';
|
||||||
|
|
||||||
|
@ -165,15 +166,32 @@ class Audio extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (!this.audioContext) {
|
const audios = document.querySelectorAll('audio');
|
||||||
this._initAudioContext();
|
|
||||||
|
audios.forEach((audio) => {
|
||||||
|
const button = audio.previousElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if(audio.paused) {
|
||||||
|
audios.forEach((e) => {
|
||||||
|
if (e !== audio) {
|
||||||
|
e.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
audio.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
audio.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.paused) {
|
this.audio.play();
|
||||||
this.setState({ paused: false }, () => this.audio.play());
|
setCurrentMedia(this.audio);
|
||||||
} else {
|
|
||||||
this.setState({ paused: true }, () => this.audio.pause());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handleResize = debounce(() => {
|
handleResize = debounce(() => {
|
||||||
|
@ -195,6 +213,7 @@ class Audio extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.audio.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
|
|
||||||
if (this.audioContext) {
|
if (this.audioContext) {
|
||||||
|
|
|
@ -19,8 +19,10 @@ import { throttle } from 'lodash';
|
||||||
|
|
||||||
import { Blurhash } from 'mastodon/components/blurhash';
|
import { Blurhash } from 'mastodon/components/blurhash';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import { playerSettings } from 'mastodon/settings';
|
||||||
|
|
||||||
import { displayMedia, useBlurhash } from '../../initial_state';
|
import { displayMedia, useBlurhash } from '../../initial_state';
|
||||||
|
import { currentMedia, setCurrentMedia } from '../../reducers/media_attachments';
|
||||||
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
import { isFullscreen, requestFullscreen, exitFullscreen } from '../ui/util/fullscreen';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -180,6 +182,7 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handlePause = () => {
|
handlePause = () => {
|
||||||
|
this.video.pause();
|
||||||
this.setState({ paused: true });
|
this.setState({ paused: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -226,8 +229,8 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
if(!isNaN(x)) {
|
if(!isNaN(x)) {
|
||||||
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
this.setState((state) => ({ volume: x, muted: state.muted && x === 0 }), () => {
|
||||||
this.video.volume = x;
|
this._syncVideoToVolumeState(x);
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState(x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 15);
|
}, 15);
|
||||||
|
@ -343,11 +346,32 @@ class Video extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
togglePlay = () => {
|
togglePlay = () => {
|
||||||
if (this.state.paused) {
|
const videos = document.querySelectorAll('video');
|
||||||
this.setState({ paused: false }, () => this.video.play());
|
|
||||||
} else {
|
videos.forEach((video) => {
|
||||||
this.setState({ paused: true }, () => this.video.pause());
|
const button = video.nextElementSibling;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
if (video.paused) {
|
||||||
|
videos.forEach((e) => {
|
||||||
|
if (e !== video) {
|
||||||
|
e.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
video.play();
|
||||||
|
this.setState({ paused: false });
|
||||||
|
} else {
|
||||||
|
video.pause();
|
||||||
|
this.setState({ paused: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentMedia !== null) {
|
||||||
|
currentMedia.pause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.video.play();
|
||||||
|
setCurrentMedia(this.video);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFullscreen = () => {
|
toggleFullscreen = () => {
|
||||||
|
@ -365,6 +389,8 @@ class Video extends PureComponent {
|
||||||
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, true);
|
||||||
|
|
||||||
window.addEventListener('scroll', this.handleScroll);
|
window.addEventListener('scroll', this.handleScroll);
|
||||||
|
|
||||||
|
this._syncVideoFromLocalStorage();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
|
@ -437,8 +463,24 @@ class Video extends PureComponent {
|
||||||
const muted = !(this.video.muted || this.state.volume === 0);
|
const muted = !(this.video.muted || this.state.volume === 0);
|
||||||
|
|
||||||
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
this.setState((state) => ({ muted, volume: Math.max(state.volume || 0.5, 0.05) }), () => {
|
||||||
this.video.volume = this.state.volume;
|
this._syncVideoToVolumeState();
|
||||||
this.video.muted = this.state.muted;
|
this._saveVolumeState();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoToVolumeState = (volume = null, muted = null) => {
|
||||||
|
this.video.volume = volume ?? this.state.volume;
|
||||||
|
this.video.muted = muted ?? this.state.muted;
|
||||||
|
};
|
||||||
|
|
||||||
|
_saveVolumeState = (volume = null, muted = null) => {
|
||||||
|
playerSettings.set('volume', volume ?? this.state.volume);
|
||||||
|
playerSettings.set('muted', muted ?? this.state.muted);
|
||||||
|
};
|
||||||
|
|
||||||
|
_syncVideoFromLocalStorage = () => {
|
||||||
|
this.setState({ volume: playerSettings.get('volume') ?? 0.5, muted: playerSettings.get('muted') ?? false }, () => {
|
||||||
|
this._syncVideoToVolumeState();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -480,6 +522,7 @@ class Video extends PureComponent {
|
||||||
|
|
||||||
handleVolumeChange = () => {
|
handleVolumeChange = () => {
|
||||||
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
this.setState({ volume: this.video.volume, muted: this.video.muted });
|
||||||
|
this._saveVolumeState(this.video.volume, this.video.muted);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleOpenVideo = () => {
|
handleOpenVideo = () => {
|
||||||
|
|
|
@ -2,6 +2,13 @@ import { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import { STORE_HYDRATE } from '../actions/store';
|
import { STORE_HYDRATE } from '../actions/store';
|
||||||
|
|
||||||
|
export let currentMedia = null;
|
||||||
|
|
||||||
|
export function setCurrentMedia(value) {
|
||||||
|
currentMedia = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
accept_content_types: [],
|
accept_content_types: [],
|
||||||
});
|
});
|
||||||
|
|
|
@ -46,4 +46,5 @@ export default class Settings {
|
||||||
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
|
export const pushNotificationsSetting = new Settings('mastodon_push_notification_data');
|
||||||
export const tagHistory = new Settings('mastodon_tag_history');
|
export const tagHistory = new Settings('mastodon_tag_history');
|
||||||
export const bannerSettings = new Settings('mastodon_banner_settings');
|
export const bannerSettings = new Settings('mastodon_banner_settings');
|
||||||
export const searchHistory = new Settings('mastodon_search_history');
|
export const searchHistory = new Settings('mastodon_search_history');
|
||||||
|
export const playerSettings = new Settings('mastodon_player');
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
.detailed-status__meta
|
.detailed-status__meta
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'name-tag', target: '_blank', rel: 'noopener noreferrer' do
|
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'name-tag', target: '_blank', rel: 'noopener noreferrer' do
|
||||||
= image_tag(status.account.avatar.url, width: 15, height: 15, alt: display_name(status.account), class: 'avatar')
|
= image_tag(status.account.avatar.url, width: 15, height: 15, alt: '', class: 'avatar')
|
||||||
.username= status.account.acct
|
.username= status.account.acct
|
||||||
·
|
·
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
|
|
|
@ -86,115 +86,6 @@ module Mastodon
|
||||||
# config.time_zone = "Central Time (US & Canada)"
|
# config.time_zone = "Central Time (US & Canada)"
|
||||||
# config.eager_load_paths << Rails.root.join("extras")
|
# config.eager_load_paths << Rails.root.join("extras")
|
||||||
|
|
||||||
# All translations from config/locales/*.rb,yml are auto loaded.
|
|
||||||
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
|
||||||
config.i18n.available_locales = [
|
|
||||||
:af,
|
|
||||||
:an,
|
|
||||||
:ar,
|
|
||||||
:ast,
|
|
||||||
:be,
|
|
||||||
:bg,
|
|
||||||
:bn,
|
|
||||||
:br,
|
|
||||||
:bs,
|
|
||||||
:ca,
|
|
||||||
:ckb,
|
|
||||||
:co,
|
|
||||||
:cs,
|
|
||||||
:cy,
|
|
||||||
:da,
|
|
||||||
:de,
|
|
||||||
:el,
|
|
||||||
:en,
|
|
||||||
:'en-GB',
|
|
||||||
:eo,
|
|
||||||
:es,
|
|
||||||
:'es-AR',
|
|
||||||
:'es-MX',
|
|
||||||
:et,
|
|
||||||
:eu,
|
|
||||||
:fa,
|
|
||||||
:fi,
|
|
||||||
:fo,
|
|
||||||
:fr,
|
|
||||||
:'fr-QC',
|
|
||||||
:fy,
|
|
||||||
:ga,
|
|
||||||
:gd,
|
|
||||||
:gl,
|
|
||||||
:he,
|
|
||||||
:hi,
|
|
||||||
:hr,
|
|
||||||
:hu,
|
|
||||||
:hy,
|
|
||||||
:id,
|
|
||||||
:ig,
|
|
||||||
:io,
|
|
||||||
:is,
|
|
||||||
:it,
|
|
||||||
:ja,
|
|
||||||
:ka,
|
|
||||||
:kab,
|
|
||||||
:kk,
|
|
||||||
:kn,
|
|
||||||
:ko,
|
|
||||||
:ku,
|
|
||||||
:kw,
|
|
||||||
:la,
|
|
||||||
:lt,
|
|
||||||
:lv,
|
|
||||||
:mk,
|
|
||||||
:ml,
|
|
||||||
:mr,
|
|
||||||
:ms,
|
|
||||||
:my,
|
|
||||||
:nl,
|
|
||||||
:nn,
|
|
||||||
:no,
|
|
||||||
:oc,
|
|
||||||
:pa,
|
|
||||||
:pl,
|
|
||||||
:'pt-BR',
|
|
||||||
:'pt-PT',
|
|
||||||
:ro,
|
|
||||||
:ru,
|
|
||||||
:sa,
|
|
||||||
:sc,
|
|
||||||
:sco,
|
|
||||||
:si,
|
|
||||||
:sk,
|
|
||||||
:sl,
|
|
||||||
:sq,
|
|
||||||
:sr,
|
|
||||||
:'sr-Latn',
|
|
||||||
:sv,
|
|
||||||
:szl,
|
|
||||||
:ta,
|
|
||||||
:te,
|
|
||||||
:th,
|
|
||||||
:tr,
|
|
||||||
:tt,
|
|
||||||
:ug,
|
|
||||||
:uk,
|
|
||||||
:ur,
|
|
||||||
:vi,
|
|
||||||
:zgh,
|
|
||||||
:'zh-CN',
|
|
||||||
:'zh-HK',
|
|
||||||
:'zh-TW',
|
|
||||||
]
|
|
||||||
|
|
||||||
config.i18n.default_locale = begin
|
|
||||||
custom_default_locale = ENV['DEFAULT_LOCALE']&.to_sym
|
|
||||||
|
|
||||||
if config.i18n.available_locales.include?(custom_default_locale)
|
|
||||||
custom_default_locale
|
|
||||||
else
|
|
||||||
:en
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
# config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
|
||||||
# config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
# config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
|
||||||
|
|
||||||
|
|
110
config/initializers/i18n.rb
Normal file
110
config/initializers/i18n.rb
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
Rails.application.configure do
|
||||||
|
config.i18n.available_locales = [
|
||||||
|
:af,
|
||||||
|
:an,
|
||||||
|
:ar,
|
||||||
|
:ast,
|
||||||
|
:be,
|
||||||
|
:bg,
|
||||||
|
:bn,
|
||||||
|
:br,
|
||||||
|
:bs,
|
||||||
|
:ca,
|
||||||
|
:ckb,
|
||||||
|
:co,
|
||||||
|
:cs,
|
||||||
|
:cy,
|
||||||
|
:da,
|
||||||
|
:de,
|
||||||
|
:el,
|
||||||
|
:en,
|
||||||
|
:'en-GB',
|
||||||
|
:eo,
|
||||||
|
:es,
|
||||||
|
:'es-AR',
|
||||||
|
:'es-MX',
|
||||||
|
:et,
|
||||||
|
:eu,
|
||||||
|
:fa,
|
||||||
|
:fi,
|
||||||
|
:fo,
|
||||||
|
:fr,
|
||||||
|
:'fr-QC',
|
||||||
|
:fy,
|
||||||
|
:ga,
|
||||||
|
:gd,
|
||||||
|
:gl,
|
||||||
|
:he,
|
||||||
|
:hi,
|
||||||
|
:hr,
|
||||||
|
:hu,
|
||||||
|
:hy,
|
||||||
|
:id,
|
||||||
|
:ig,
|
||||||
|
:io,
|
||||||
|
:is,
|
||||||
|
:it,
|
||||||
|
:ja,
|
||||||
|
:ka,
|
||||||
|
:kab,
|
||||||
|
:kk,
|
||||||
|
:kn,
|
||||||
|
:ko,
|
||||||
|
:ku,
|
||||||
|
:kw,
|
||||||
|
:la,
|
||||||
|
:lt,
|
||||||
|
:lv,
|
||||||
|
:mk,
|
||||||
|
:ml,
|
||||||
|
:mr,
|
||||||
|
:ms,
|
||||||
|
:my,
|
||||||
|
:nl,
|
||||||
|
:nn,
|
||||||
|
:no,
|
||||||
|
:oc,
|
||||||
|
:pa,
|
||||||
|
:pl,
|
||||||
|
:'pt-BR',
|
||||||
|
:'pt-PT',
|
||||||
|
:ro,
|
||||||
|
:ru,
|
||||||
|
:sa,
|
||||||
|
:sc,
|
||||||
|
:sco,
|
||||||
|
:si,
|
||||||
|
:sk,
|
||||||
|
:sl,
|
||||||
|
:sq,
|
||||||
|
:sr,
|
||||||
|
:'sr-Latn',
|
||||||
|
:sv,
|
||||||
|
:szl,
|
||||||
|
:ta,
|
||||||
|
:te,
|
||||||
|
:th,
|
||||||
|
:tr,
|
||||||
|
:tt,
|
||||||
|
:ug,
|
||||||
|
:uk,
|
||||||
|
:ur,
|
||||||
|
:vi,
|
||||||
|
:zgh,
|
||||||
|
:'zh-CN',
|
||||||
|
:'zh-HK',
|
||||||
|
:'zh-TW',
|
||||||
|
]
|
||||||
|
|
||||||
|
config.i18n.default_locale = begin
|
||||||
|
custom_default_locale = ENV['DEFAULT_LOCALE']&.to_sym
|
||||||
|
|
||||||
|
if Rails.configuration.i18n.available_locales.include?(custom_default_locale)
|
||||||
|
custom_default_locale
|
||||||
|
else
|
||||||
|
:en
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,6 +3,18 @@
|
||||||
require 'sidekiq_unique_jobs/web'
|
require 'sidekiq_unique_jobs/web'
|
||||||
require 'sidekiq-scheduler/web'
|
require 'sidekiq-scheduler/web'
|
||||||
|
|
||||||
|
class RedirectWithVary < ActionDispatch::Routing::PathRedirect
|
||||||
|
def build_response(req)
|
||||||
|
super.tap do |response|
|
||||||
|
response.headers['Vary'] = 'Origin, Accept'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect_with_vary(path)
|
||||||
|
RedirectWithVary.new(301, path)
|
||||||
|
end
|
||||||
|
|
||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
# Paths of routes on the web app that to not require to be indexed or
|
# Paths of routes on the web app that to not require to be indexed or
|
||||||
# have alternative format representations requiring separate controllers
|
# have alternative format representations requiring separate controllers
|
||||||
|
@ -91,10 +103,13 @@ Rails.application.routes.draw do
|
||||||
confirmations: 'auth/confirmations',
|
confirmations: 'auth/confirmations',
|
||||||
}
|
}
|
||||||
|
|
||||||
get '/users/:username', to: redirect('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
# rubocop:disable Style/FormatStringToken - those do not go through the usual formatting functions and are not safe to correct
|
||||||
get '/users/:username/following', to: redirect('/@%{username}/following'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username', to: redirect_with_vary('/@%{username}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
get '/users/:username/followers', to: redirect('/@%{username}/followers'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username/following', to: redirect_with_vary('/@%{username}/following'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
get '/users/:username/statuses/:id', to: redirect('/@%{username}/%{id}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
get '/users/:username/followers', to: redirect_with_vary('/@%{username}/followers'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
|
get '/users/:username/statuses/:id', to: redirect_with_vary('/@%{username}/%{id}'), constraints: lambda { |req| req.format.nil? || req.format.html? }
|
||||||
|
# rubocop:enable Style/FormatStringToken
|
||||||
|
|
||||||
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
get '/authorize_follow', to: redirect { |_, request| "/authorize_interaction?#{request.params.to_query}" }
|
||||||
|
|
||||||
resources :accounts, path: 'users', only: [:show], param: :username do
|
resources :accounts, path: 'users', only: [:show], param: :username do
|
||||||
|
|
|
@ -136,24 +136,24 @@ module Mastodon::CLI
|
||||||
Mastodon has to be stopped to run this task, which will take a long time and may be destructive.
|
Mastodon has to be stopped to run this task, which will take a long time and may be destructive.
|
||||||
LONG_DESC
|
LONG_DESC
|
||||||
def fix_duplicates
|
def fix_duplicates
|
||||||
if ActiveRecord::Migrator.current_version < MIN_SUPPORTED_VERSION
|
verify_system_ready!
|
||||||
say 'Your version of the database schema is too old and is not supported by this script.', :red
|
|
||||||
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
|
|
||||||
exit(1)
|
|
||||||
elsif ActiveRecord::Migrator.current_version > MAX_SUPPORTED_VERSION
|
|
||||||
say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
|
|
||||||
exit(1) unless yes?('Continue anyway? (Yes/No)')
|
|
||||||
end
|
|
||||||
|
|
||||||
if Sidekiq::ProcessSet.new.any?
|
process_deduplications
|
||||||
say 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.', :red
|
|
||||||
exit(1)
|
|
||||||
end
|
|
||||||
|
|
||||||
say 'This task will take a long time to run and is potentially destructive.', :yellow
|
deduplication_cleanup_tasks
|
||||||
say 'Please make sure to stop Mastodon and have a backup.', :yellow
|
|
||||||
exit(1) unless yes?('Continue? (Yes/No)')
|
|
||||||
|
|
||||||
|
say 'Finished!'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def verify_system_ready!
|
||||||
|
verify_schema_version!
|
||||||
|
verify_sidekiq_not_active!
|
||||||
|
verify_backup_warning!
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_deduplications
|
||||||
deduplicate_users!
|
deduplicate_users!
|
||||||
deduplicate_account_domain_blocks!
|
deduplicate_account_domain_blocks!
|
||||||
deduplicate_account_identity_proofs!
|
deduplicate_account_identity_proofs!
|
||||||
|
@ -173,14 +173,44 @@ module Mastodon::CLI
|
||||||
deduplicate_webauthn_credentials!
|
deduplicate_webauthn_credentials!
|
||||||
deduplicate_webhooks!
|
deduplicate_webhooks!
|
||||||
deduplicate_software_updates!
|
deduplicate_software_updates!
|
||||||
|
|
||||||
Scenic.database.refresh_materialized_view('instances', concurrently: true, cascade: false) if ActiveRecord::Migrator.current_version >= 2020_12_06_004238
|
|
||||||
Rails.cache.clear
|
|
||||||
|
|
||||||
say 'Finished!'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def deduplication_cleanup_tasks
|
||||||
|
refresh_instances_view if schema_has_instances_view?
|
||||||
|
Rails.cache.clear
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh_instances_view
|
||||||
|
Scenic.database.refresh_materialized_view('instances', concurrently: true, cascade: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def schema_has_instances_view?
|
||||||
|
ActiveRecord::Migrator.current_version >= 2020_12_06_004238
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_schema_version!
|
||||||
|
if ActiveRecord::Migrator.current_version < MIN_SUPPORTED_VERSION
|
||||||
|
say 'Your version of the database schema is too old and is not supported by this script.', :red
|
||||||
|
say 'Please update to at least Mastodon 3.0.0 before running this script.', :red
|
||||||
|
exit(1)
|
||||||
|
elsif ActiveRecord::Migrator.current_version > MAX_SUPPORTED_VERSION
|
||||||
|
say 'Your version of the database schema is more recent than this script, this may cause unexpected errors.', :yellow
|
||||||
|
exit(1) unless yes?('Continue anyway? (Yes/No)')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_sidekiq_not_active!
|
||||||
|
if Sidekiq::ProcessSet.new.any?
|
||||||
|
say 'It seems Sidekiq is running. All Mastodon processes need to be stopped when using this script.', :red
|
||||||
|
exit(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def verify_backup_warning!
|
||||||
|
say 'This task will take a long time to run and is potentially destructive.', :yellow
|
||||||
|
say 'Please make sure to stop Mastodon and have a backup.', :yellow
|
||||||
|
exit(1) unless yes?('Continue? (Yes/No)')
|
||||||
|
end
|
||||||
|
|
||||||
def deduplicate_accounts!
|
def deduplicate_accounts!
|
||||||
remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')
|
remove_index_if_exists!(:accounts, 'index_accounts_on_username_and_domain_lower')
|
||||||
|
|
|
@ -19,10 +19,6 @@ RSpec.describe Admin::Settings::BrandingController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PUT #update' do
|
describe 'PUT #update' do
|
||||||
before do
|
|
||||||
allow_any_instance_of(Form::AdminSettings).to receive(:valid?).and_return(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
around do |example|
|
around do |example|
|
||||||
before = Setting.site_short_description
|
before = Setting.site_short_description
|
||||||
Setting.site_short_description = nil
|
Setting.site_short_description = nil
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :new
|
get :new
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
@ -19,7 +19,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
sign_in(user, scope: :user)
|
sign_in(user, scope: :user)
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
@ -66,7 +66,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
user.approved = false
|
user.approved = false
|
||||||
user.save!
|
user.save!
|
||||||
sign_in(user, scope: :user)
|
sign_in(user, scope: :user)
|
||||||
|
@ -83,7 +83,7 @@ describe Auth::ConfirmationsController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
allow(BootstrapTimelineWorker).to receive(:perform_async)
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :show, params: { confirmation_token: 'foobar' }
|
get :show, params: { confirmation_token: 'foobar' }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ describe Auth::PasswordsController do
|
||||||
|
|
||||||
describe 'GET #new' do
|
describe 'GET #new' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.env['devise.mapping'] = Devise.mappings[:user]
|
request.env['devise.mapping'] = Devise.mappings[:user]
|
||||||
get :new
|
get :new
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
|
|
|
@ -378,7 +378,7 @@ RSpec.describe Auth::SessionsController do
|
||||||
|
|
||||||
context 'when using a valid webauthn credential' do
|
context 'when using a valid webauthn credential' do
|
||||||
before do
|
before do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
|
post :create, params: { user: { credential: fake_credential } }, session: { attempt_user_id: user.id, attempt_user_updated_at: user.updated_at.to_s }
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,13 +8,11 @@ describe EmojisController do
|
||||||
let(:emoji) { Fabricate(:custom_emoji) }
|
let(:emoji) { Fabricate(:custom_emoji) }
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
subject(:body) { JSON.parse(response.body, symbolize_names: true) }
|
|
||||||
|
|
||||||
let(:response) { get :show, params: { id: emoji.id, format: :json } }
|
let(:response) { get :show, params: { id: emoji.id, format: :json } }
|
||||||
|
|
||||||
it 'returns the right response' do
|
it 'returns the right response' do
|
||||||
expect(response).to have_http_status 200
|
expect(response).to have_http_status 200
|
||||||
expect(body[:name]).to eq ':coolcat:'
|
expect(body_as_json[:name]).to eq ':coolcat:'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ RSpec.describe HomeController do
|
||||||
|
|
||||||
context 'when not signed in' do
|
context 'when not signed in' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@request.path = '/'
|
request.path = '/'
|
||||||
expect(subject).to have_http_status(:success)
|
expect(subject).to have_http_status(:success)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -130,7 +130,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
it 'stores the challenge on the session' do
|
it 'stores the challenge on the session' do
|
||||||
get :options
|
get :options
|
||||||
|
|
||||||
expect(@controller.session[:webauthn_challenge]).to be_present
|
expect(controller.session[:webauthn_challenge]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not change webauthn_id' do
|
it 'does not change webauthn_id' do
|
||||||
|
@ -155,7 +155,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
it 'stores the challenge on the session' do
|
it 'stores the challenge on the session' do
|
||||||
get :options
|
get :options
|
||||||
|
|
||||||
expect(@controller.session[:webauthn_challenge]).to be_present
|
expect(controller.session[:webauthn_challenge]).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets user webauthn_id' do
|
it 'sets user webauthn_id' do
|
||||||
|
@ -218,7 +218,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
|
|
||||||
context 'when creation succeeds' do
|
context 'when creation succeeds' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
||||||
|
@ -226,7 +226,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'adds a new credential to user credentials' do
|
it 'adds a new credential to user credentials' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
@ -234,7 +234,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not change webauthn_id' do
|
it 'does not change webauthn_id' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
@ -244,7 +244,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
|
|
||||||
context 'when the nickname is already used' do
|
context 'when the nickname is already used' do
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' }
|
post :create, params: { credential: new_webauthn_credential, nickname: 'USB Key' }
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'fails' do
|
it 'fails' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ describe Settings::TwoFactorAuthentication::WebauthnCredentialsController do
|
||||||
context 'when user have not enabled webauthn' do
|
context 'when user have not enabled webauthn' do
|
||||||
context 'when creation succeeds' do
|
context 'when creation succeeds' do
|
||||||
it 'creates a webauthn credential' do
|
it 'creates a webauthn credential' do
|
||||||
@controller.session[:webauthn_challenge] = challenge
|
controller.session[:webauthn_challenge] = challenge
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
post :create, params: { credential: new_webauthn_credential, nickname: nickname }
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe AccountStatusesFilter do
|
RSpec.describe AccountStatusesFilter do
|
||||||
subject { described_class.new(account, current_account, params) }
|
|
||||||
|
|
||||||
let(:account) { Fabricate(:account) }
|
let(:account) { Fabricate(:account) }
|
||||||
let(:current_account) { nil }
|
let(:current_account) { nil }
|
||||||
let(:params) { {} }
|
let(:params) { {} }
|
||||||
|
@ -38,6 +36,8 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#results' do
|
describe '#results' do
|
||||||
|
subject { described_class.new(account, current_account, params).results }
|
||||||
|
|
||||||
let(:tag) { Fabricate(:tag) }
|
let(:tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
@ -56,7 +56,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:params) { { only_media: true } }
|
let(:params) { { only_media: true } }
|
||||||
|
|
||||||
it 'returns only statuses with media' do
|
it 'returns only statuses with media' do
|
||||||
expect(subject.results.all?(&:with_media?)).to be true
|
expect(subject.all?(&:with_media?)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:params) { { tagged: tag.name } }
|
let(:params) { { tagged: tag.name } }
|
||||||
|
|
||||||
it 'returns only statuses with tag' do
|
it 'returns only statuses with tag' do
|
||||||
expect(subject.results.all? { |s| s.tags.include?(tag) }).to be true
|
expect(subject.all? { |s| s.tags.include?(tag) }).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:params) { { exclude_replies: true } }
|
let(:params) { { exclude_replies: true } }
|
||||||
|
|
||||||
it 'returns only statuses that are not replies' do
|
it 'returns only statuses that are not replies' do
|
||||||
expect(subject.results.none?(&:reply?)).to be true
|
expect(subject.none?(&:reply?)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:params) { { exclude_reblogs: true } }
|
let(:params) { { exclude_reblogs: true } }
|
||||||
|
|
||||||
it 'returns only statuses that are not reblogs' do
|
it 'returns only statuses that are not reblogs' do
|
||||||
expect(subject.results.none?(&:reblog?)).to be true
|
expect(subject.none?(&:reblog?)).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -89,16 +89,12 @@ RSpec.describe AccountStatusesFilter do
|
||||||
let(:current_account) { nil }
|
let(:current_account) { nil }
|
||||||
let(:direct_status) { nil }
|
let(:direct_status) { nil }
|
||||||
|
|
||||||
it 'returns only public statuses' do
|
it 'returns only public statuses, public replies, and public reblogs' do
|
||||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns public replies' do
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns public reblogs' do
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'filter params'
|
it_behaves_like 'filter params'
|
||||||
|
@ -112,23 +108,19 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns nothing' do
|
it 'returns nothing' do
|
||||||
expect(subject.results.to_a).to be_empty
|
expect(subject.to_a).to be_empty
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when accessed by self' do
|
context 'when accessed by self' do
|
||||||
let(:current_account) { account }
|
let(:current_account) { account }
|
||||||
|
|
||||||
it 'returns everything' do
|
it 'returns all statuses, replies, and reblogs' do
|
||||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(direct private unlisted public)
|
expect(results_unique_visibilities).to match_array %w(direct private unlisted public)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns replies' do
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns reblogs' do
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'filter params'
|
it_behaves_like 'filter params'
|
||||||
|
@ -141,23 +133,19 @@ RSpec.describe AccountStatusesFilter do
|
||||||
current_account.follow!(account)
|
current_account.follow!(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns private statuses' do
|
it 'returns private statuses, replies, and reblogs' do
|
||||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(private unlisted public)
|
expect(results_unique_visibilities).to match_array %w(private unlisted public)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns replies' do
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns reblogs' do
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when there is a direct status mentioning the non-follower' do
|
context 'when there is a direct status mentioning the non-follower' do
|
||||||
let!(:direct_status) { status_with_mention!(:direct, current_account) }
|
let!(:direct_status) { status_with_mention!(:direct, current_account) }
|
||||||
|
|
||||||
it 'returns the direct status' do
|
it 'returns the direct status' do
|
||||||
expect(subject.results.pluck(:id)).to include(direct_status.id)
|
expect(results_ids).to include(direct_status.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -167,23 +155,19 @@ RSpec.describe AccountStatusesFilter do
|
||||||
context 'when accessed by a non-follower' do
|
context 'when accessed by a non-follower' do
|
||||||
let(:current_account) { Fabricate(:account) }
|
let(:current_account) { Fabricate(:account) }
|
||||||
|
|
||||||
it 'returns only public statuses' do
|
it 'returns only public statuses, replies, and reblogs' do
|
||||||
expect(subject.results.pluck(:visibility).uniq).to match_array %w(unlisted public)
|
expect(results_unique_visibilities).to match_array %w(unlisted public)
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns public replies' do
|
expect(results_in_reply_to_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:in_reply_to_id)).to_not be_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns public reblogs' do
|
expect(results_reblog_of_ids).to_not be_empty
|
||||||
expect(subject.results.pluck(:reblog_of_id)).to_not be_empty
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when there is a private status mentioning the non-follower' do
|
context 'when there is a private status mentioning the non-follower' do
|
||||||
let!(:private_status) { status_with_mention!(:private, current_account) }
|
let!(:private_status) { status_with_mention!(:private, current_account) }
|
||||||
|
|
||||||
it 'returns the private status' do
|
it 'returns the private status' do
|
||||||
expect(subject.results.pluck(:id)).to include(private_status.id)
|
expect(results_ids).to include(private_status.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -195,7 +179,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return reblog of blocked account' do
|
it 'does not return reblog of blocked account' do
|
||||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
expect(results_ids).to_not include(reblog.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -209,7 +193,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return reblog of blocked domain' do
|
it 'does not return reblog of blocked domain' do
|
||||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
expect(results_ids).to_not include(reblog.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -223,7 +207,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the reblog from the non-blocked domain' do
|
it 'returns the reblog from the non-blocked domain' do
|
||||||
expect(subject.results.pluck(:id)).to include(reblog.id)
|
expect(results_ids).to include(reblog.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -235,7 +219,7 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return reblog of muted account' do
|
it 'does not return reblog of muted account' do
|
||||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
expect(results_ids).to_not include(reblog.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -247,11 +231,29 @@ RSpec.describe AccountStatusesFilter do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not return reblog of blocked-by account' do
|
it 'does not return reblog of blocked-by account' do
|
||||||
expect(subject.results.pluck(:id)).to_not include(reblog.id)
|
expect(results_ids).to_not include(reblog.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'filter params'
|
it_behaves_like 'filter params'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def results_unique_visibilities
|
||||||
|
subject.pluck(:visibility).uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def results_in_reply_to_ids
|
||||||
|
subject.pluck(:in_reply_to_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def results_reblog_of_ids
|
||||||
|
subject.pluck(:reblog_of_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def results_ids
|
||||||
|
subject.pluck(:id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,9 +4,51 @@ require 'rails_helper'
|
||||||
require 'mastodon/cli/maintenance'
|
require 'mastodon/cli/maintenance'
|
||||||
|
|
||||||
describe Mastodon::CLI::Maintenance do
|
describe Mastodon::CLI::Maintenance do
|
||||||
|
let(:cli) { described_class.new }
|
||||||
|
|
||||||
describe '.exit_on_failure?' do
|
describe '.exit_on_failure?' do
|
||||||
it 'returns true' do
|
it 'returns true' do
|
||||||
expect(described_class.exit_on_failure?).to be true
|
expect(described_class.exit_on_failure?).to be true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#fix_duplicates' do
|
||||||
|
context 'when the database version is too old' do
|
||||||
|
before do
|
||||||
|
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2000_01_01_000000) # Earlier than minimum
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'Exits with error message' do
|
||||||
|
expect { cli.invoke :fix_duplicates }.to output(
|
||||||
|
a_string_including('is too old')
|
||||||
|
).to_stdout.and raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the database version is too new and the user does not continue' do
|
||||||
|
before do
|
||||||
|
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2100_01_01_000000) # Later than maximum
|
||||||
|
allow(cli.shell).to receive(:yes?).with('Continue anyway? (Yes/No)').and_return(false).once
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'Exits with error message' do
|
||||||
|
expect { cli.invoke :fix_duplicates }.to output(
|
||||||
|
a_string_including('more recent')
|
||||||
|
).to_stdout.and raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when Sidekiq is running' do
|
||||||
|
before do
|
||||||
|
allow(ActiveRecord::Migrator).to receive(:current_version).and_return(2022_01_01_000000) # Higher than minimum, lower than maximum
|
||||||
|
allow(Sidekiq::ProcessSet).to receive(:new).and_return [:process]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'Exits with error message' do
|
||||||
|
expect { cli.invoke :fix_duplicates }.to output(
|
||||||
|
a_string_including('Sidekiq is running')
|
||||||
|
).to_stdout.and raise_error(SystemExit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -124,7 +124,7 @@ describe 'Caching behavior' do
|
||||||
expect(response.cookies).to be_empty
|
expect(response.cookies).to be_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets public cache control' do
|
it 'sets public cache control', :aggregate_failures do
|
||||||
# expect(response.cache_control[:max_age]&.to_i).to be_positive
|
# expect(response.cache_control[:max_age]&.to_i).to be_positive
|
||||||
expect(response.cache_control[:public]).to be_truthy
|
expect(response.cache_control[:public]).to be_truthy
|
||||||
expect(response.cache_control[:private]).to be_falsy
|
expect(response.cache_control[:private]).to be_falsy
|
||||||
|
@ -141,11 +141,8 @@ describe 'Caching behavior' do
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'non-cacheable error' do
|
shared_examples 'non-cacheable error' do
|
||||||
it 'does not return HTTP success' do
|
it 'does not return HTTP success and does not have cache headers', :aggregate_failures do
|
||||||
expect(response).to_not have_http_status(200)
|
expect(response).to_not have_http_status(200)
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not have cache headers' do
|
|
||||||
expect(response.cache_control[:public]).to be_falsy
|
expect(response.cache_control[:public]).to be_falsy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -171,17 +168,24 @@ describe 'Caching behavior' do
|
||||||
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
|
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Moderator')) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
# rubocop:disable Style/NumericLiterals
|
status = Fabricate(:status, account: alice, id: '110224538612341312')
|
||||||
status = Fabricate(:status, account: alice, id: 110224538612341312)
|
Fabricate(:status, account: alice, id: '110224538643211312', visibility: :private)
|
||||||
Fabricate(:status, account: alice, id: 110224538643211312, visibility: :private)
|
|
||||||
Fabricate(:invite, code: 'abcdef')
|
Fabricate(:invite, code: 'abcdef')
|
||||||
Fabricate(:poll, status: status, account: alice, id: 12345)
|
Fabricate(:poll, status: status, account: alice, id: '12345')
|
||||||
# rubocop:enable Style/NumericLiterals
|
|
||||||
|
|
||||||
user.account.follow!(alice)
|
user.account.follow!(alice)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when anonymously accessed' do
|
context 'when anonymously accessed' do
|
||||||
|
describe '/users/alice' do
|
||||||
|
it 'redirects with proper cache header', :aggregate_failures do
|
||||||
|
get '/users/alice'
|
||||||
|
|
||||||
|
expect(response).to redirect_to('/@alice')
|
||||||
|
expect(response.headers['Vary']&.split(',')&.map { |x| x.strip.downcase }).to include('accept')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
TestEndpoints::ALWAYS_CACHED.each do |endpoint|
|
||||||
describe endpoint do
|
describe endpoint do
|
||||||
before { get endpoint }
|
before { get endpoint }
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::DeviceSerializer do
|
describe ActivityPub::DeviceSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Fabricate(:device) }
|
let(:record) { Fabricate(:device) }
|
||||||
|
|
||||||
describe 'type' do
|
describe 'type' do
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::NoteSerializer do
|
describe ActivityPub::NoteSerializer do
|
||||||
subject { JSON.parse(@serialization.to_json) }
|
subject { serialized_record_json(parent, described_class, adapter: ActivityPub::Adapter) }
|
||||||
|
|
||||||
let!(:account) { Fabricate(:account) }
|
let!(:account) { Fabricate(:account) }
|
||||||
let!(:other) { Fabricate(:account) }
|
let!(:other) { Fabricate(:account) }
|
||||||
|
@ -14,10 +14,6 @@ describe ActivityPub::NoteSerializer do
|
||||||
let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
|
let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
|
||||||
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
|
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
|
||||||
|
|
||||||
before do
|
|
||||||
@serialization = ActiveModelSerializers::SerializableResource.new(parent, serializer: described_class, adapter: ActivityPub::Adapter)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has the expected shape' do
|
it 'has the expected shape' do
|
||||||
expect(subject).to include({
|
expect(subject).to include({
|
||||||
'@context' => include('https://www.w3.org/ns/activitystreams'),
|
'@context' => include('https://www.w3.org/ns/activitystreams'),
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::OneTimeKeySerializer do
|
describe ActivityPub::OneTimeKeySerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Fabricate(:one_time_key) }
|
let(:record) { Fabricate(:one_time_key) }
|
||||||
|
|
||||||
describe 'type' do
|
describe 'type' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::UndoLikeSerializer do
|
describe ActivityPub::UndoLikeSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Fabricate(:favourite) }
|
let(:record) { Fabricate(:favourite) }
|
||||||
|
|
||||||
describe 'type' do
|
describe 'type' do
|
||||||
|
|
|
@ -3,16 +3,12 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::UpdatePollSerializer do
|
describe ActivityPub::UpdatePollSerializer do
|
||||||
subject { JSON.parse(@serialization.to_json) }
|
subject { serialized_record_json(status, described_class, adapter: ActivityPub::Adapter) }
|
||||||
|
|
||||||
let(:account) { Fabricate(:account) }
|
let(:account) { Fabricate(:account) }
|
||||||
let(:poll) { Fabricate(:poll, account: account) }
|
let(:poll) { Fabricate(:poll, account: account) }
|
||||||
let!(:status) { Fabricate(:status, account: account, poll: poll) }
|
let!(:status) { Fabricate(:status, account: account, poll: poll) }
|
||||||
|
|
||||||
before do
|
|
||||||
@serialization = ActiveModelSerializers::SerializableResource.new(status, serializer: described_class, adapter: ActivityPub::Adapter)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'has a Update type' do
|
it 'has a Update type' do
|
||||||
expect(subject['type']).to eql('Update')
|
expect(subject['type']).to eql('Update')
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe ActivityPub::VoteSerializer do
|
describe ActivityPub::VoteSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Fabricate(:poll_vote) }
|
let(:record) { Fabricate(:poll_vote) }
|
||||||
|
|
||||||
describe 'type' do
|
describe 'type' do
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::AccountSerializer do
|
describe REST::AccountSerializer do
|
||||||
subject { JSON.parse(ActiveModelSerializers::SerializableResource.new(account, serializer: described_class).to_json) }
|
subject { serialized_record_json(account, described_class) }
|
||||||
|
|
||||||
let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) }
|
let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) }
|
||||||
let(:user) { Fabricate(:user, role: role) }
|
let(:user) { Fabricate(:user, role: role) }
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::EncryptedMessageSerializer do
|
describe REST::EncryptedMessageSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Fabricate(:encrypted_message) }
|
let(:record) { Fabricate(:encrypted_message) }
|
||||||
|
|
||||||
describe 'account' do
|
describe 'account' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::InstanceSerializer do
|
describe REST::InstanceSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { InstancePresenter.new }
|
let(:record) { InstancePresenter.new }
|
||||||
|
|
||||||
describe 'usage' do
|
describe 'usage' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::Keys::ClaimResultSerializer do
|
describe REST::Keys::ClaimResultSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Keys::ClaimService::Result.new(Account.new(id: 123), 456) }
|
let(:record) { Keys::ClaimService::Result.new(Account.new(id: 123), 456) }
|
||||||
|
|
||||||
describe 'account' do
|
describe 'account' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::Keys::DeviceSerializer do
|
describe REST::Keys::DeviceSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Device.new(name: 'Device name') }
|
let(:record) { Device.new(name: 'Device name') }
|
||||||
|
|
||||||
describe 'name' do
|
describe 'name' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::Keys::QueryResultSerializer do
|
describe REST::Keys::QueryResultSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) { Keys::QueryService::Result.new(Account.new(id: 123), []) }
|
let(:record) { Keys::QueryService::Result.new(Account.new(id: 123), []) }
|
||||||
|
|
||||||
describe 'account' do
|
describe 'account' do
|
||||||
|
|
|
@ -3,13 +3,7 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
describe REST::SuggestionSerializer do
|
describe REST::SuggestionSerializer do
|
||||||
let(:serialization) do
|
let(:serialization) { serialized_record_json(record, described_class) }
|
||||||
JSON.parse(
|
|
||||||
ActiveModelSerializers::SerializableResource.new(
|
|
||||||
record, serializer: described_class
|
|
||||||
).to_json
|
|
||||||
)
|
|
||||||
end
|
|
||||||
let(:record) do
|
let(:record) do
|
||||||
AccountSuggestions::Suggestion.new(
|
AccountSuggestions::Suggestion.new(
|
||||||
account: account,
|
account: account,
|
||||||
|
|
|
@ -52,6 +52,17 @@ def json_str_to_hash(str)
|
||||||
JSON.parse(str, symbolize_names: true)
|
JSON.parse(str, symbolize_names: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def serialized_record_json(record, serializer, adapter: nil)
|
||||||
|
options = { serializer: serializer }
|
||||||
|
options[:adapter] = adapter if adapter.present?
|
||||||
|
JSON.parse(
|
||||||
|
ActiveModelSerializers::SerializableResource.new(
|
||||||
|
record,
|
||||||
|
options
|
||||||
|
).to_json
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def expect_push_bulk_to_match(klass, matcher)
|
def expect_push_bulk_to_match(klass, matcher)
|
||||||
allow(Sidekiq::Client).to receive(:push_bulk)
|
allow(Sidekiq::Client).to receive(:push_bulk)
|
||||||
yield
|
yield
|
||||||
|
@ -60,122 +71,3 @@ def expect_push_bulk_to_match(klass, matcher)
|
||||||
'args' => matcher,
|
'args' => matcher,
|
||||||
}))
|
}))
|
||||||
end
|
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
|
|
||||||
|
|
||||||
class SearchDataManager
|
|
||||||
def prepare_test_data
|
|
||||||
4.times do |i|
|
|
||||||
username = "search_test_account_#{i}"
|
|
||||||
account = Fabricate.create(:account, username: username, indexable: i.even?, discoverable: i.even?, note: "Lover of #{i}.")
|
|
||||||
2.times do |j|
|
|
||||||
Fabricate.create(:status, account: account, text: "#{username}'s #{j} post", visibility: j.even? ? :public : :private)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
3.times do |i|
|
|
||||||
Fabricate.create(:tag, name: "search_test_tag_#{i}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def indexes
|
|
||||||
[
|
|
||||||
AccountsIndex,
|
|
||||||
PublicStatusesIndex,
|
|
||||||
StatusesIndex,
|
|
||||||
TagsIndex,
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
def populate_indexes
|
|
||||||
indexes.each do |index_class|
|
|
||||||
index_class.purge!
|
|
||||||
index_class.import!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_indexes
|
|
||||||
indexes.each(&:delete!)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cleanup_test_data
|
|
||||||
Status.destroy_all
|
|
||||||
Account.destroy_all
|
|
||||||
Tag.destroy_all
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
43
spec/support/search_data_manager.rb
Normal file
43
spec/support/search_data_manager.rb
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class SearchDataManager
|
||||||
|
def prepare_test_data
|
||||||
|
4.times do |i|
|
||||||
|
username = "search_test_account_#{i}"
|
||||||
|
account = Fabricate.create(:account, username: username, indexable: i.even?, discoverable: i.even?, note: "Lover of #{i}.")
|
||||||
|
2.times do |j|
|
||||||
|
Fabricate.create(:status, account: account, text: "#{username}'s #{j} post", visibility: j.even? ? :public : :private)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
3.times do |i|
|
||||||
|
Fabricate.create(:tag, name: "search_test_tag_#{i}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def indexes
|
||||||
|
[
|
||||||
|
AccountsIndex,
|
||||||
|
PublicStatusesIndex,
|
||||||
|
StatusesIndex,
|
||||||
|
TagsIndex,
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def populate_indexes
|
||||||
|
indexes.each do |index_class|
|
||||||
|
index_class.purge!
|
||||||
|
index_class.import!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_indexes
|
||||||
|
indexes.each(&:delete!)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cleanup_test_data
|
||||||
|
Status.destroy_all
|
||||||
|
Account.destroy_all
|
||||||
|
Tag.destroy_all
|
||||||
|
end
|
||||||
|
end
|
78
spec/support/streaming_server_manager.rb
Normal file
78
spec/support/streaming_server_manager.rb
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in a new issue