NodeInfo: replace Int by Long in upload limits, add test cases

This commit is contained in:
Alibek Omarov 2020-05-23 14:57:14 +03:00
parent 25b57f3db4
commit 930e05be27
6 changed files with 140 additions and 28 deletions

View File

@ -122,7 +122,8 @@ class ComposeActivity : BaseActivity(),
var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT var maximumTootCharacters = DEFAULT_CHARACTER_LIMIT
private var composeOptions: ComposeOptions? = null private var composeOptions: ComposeOptions? = null
private val viewModel: ComposeViewModel by viewModels { viewModelFactory } @VisibleForTesting
val viewModel: ComposeViewModel by viewModels { viewModelFactory }
private var suggestFormattingSyntax: String = "text/markdown" private var suggestFormattingSyntax: String = "text/markdown"
private var mediaCount = 0 private var mediaCount = 0
@ -346,8 +347,9 @@ class ComposeActivity : BaseActivity(),
enableButton(composeAddMediaButton, true, true) enableButton(composeAddMediaButton, true, true)
enablePollButton(true) enablePollButton(true)
} }
private var supportedFormattingSyntax = arrayListOf<String>() @VisibleForTesting
var supportedFormattingSyntax = arrayListOf<String>()
private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) { private fun subscribeToUpdates(mediaAdapter: MediaPreviewAdapter) {
withLifecycleContext { withLifecycleContext {

View File

@ -560,8 +560,8 @@ fun <T> mutableLiveData(default: T) = MutableLiveData<T>().apply { value = defau
const val DEFAULT_CHARACTER_LIMIT = 500 const val DEFAULT_CHARACTER_LIMIT = 500
private const val DEFAULT_MAX_OPTION_COUNT = 4 private const val DEFAULT_MAX_OPTION_COUNT = 4
private const val DEFAULT_MAX_OPTION_LENGTH = 25 private const val DEFAULT_MAX_OPTION_LENGTH = 25
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB private const val STATUS_VIDEO_SIZE_LIMIT : Long = 41943040 // 40MiB
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB private const val STATUS_IMAGE_SIZE_LIMIT : Long = 8388608 // 8MiB
data class ComposeInstanceParams( data class ComposeInstanceParams(
@ -576,6 +576,6 @@ data class ComposeInstanceMetadata(
val supportsMarkdown: Boolean, val supportsMarkdown: Boolean,
val supportsBBcode: Boolean, val supportsBBcode: Boolean,
val supportsHTML: Boolean, val supportsHTML: Boolean,
val videoLimit: Int, val videoLimit: Long,
val imageLimit: Int val imageLimit: Long
) )

View File

@ -75,7 +75,7 @@ public class DownsizeImageTask extends AsyncTask<Uri, Void, Boolean> {
super.onPostExecute(successful); super.onPostExecute(successful);
} }
public static boolean resize(Uri[] uris, int sizeLimit, ContentResolver contentResolver, public static boolean resize(Uri[] uris, long sizeLimit, ContentResolver contentResolver,
File tempFile) { File tempFile) {
for (Uri uri : uris) { for (Uri uri : uris) {
InputStream inputStream; InputStream inputStream;

View File

@ -59,8 +59,8 @@ fun createNewImageFile(context: Context): File {
data class PreparedMedia(val type: Int, val uri: Uri, val size: Long) data class PreparedMedia(val type: Int, val uri: Uri, val size: Long)
interface MediaUploader { interface MediaUploader {
fun prepareMedia(inUri: Uri, videoLimit: Int, imageLimit: Int, filename: String?): Single<PreparedMedia> fun prepareMedia(inUri: Uri, videoLimit: Long, imageLimit: Long, filename: String?): Single<PreparedMedia>
fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent> fun uploadMedia(media: QueuedMedia, videoLimit: Long, imageLimit: Long): Observable<UploadEvent>
} }
class AudioSizeException : Exception() class AudioSizeException : Exception()
@ -73,14 +73,14 @@ class MediaUploaderImpl(
private val context: Context, private val context: Context,
private val mastodonApi: MastodonApi private val mastodonApi: MastodonApi
) : MediaUploader { ) : MediaUploader {
override fun uploadMedia(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent> { override fun uploadMedia(media: QueuedMedia, videoLimit: Long, imageLimit: Long): Observable<UploadEvent> {
return Observable return Observable
.fromCallable { .fromCallable {
if (shouldResizeMedia(media, imageLimit)) { if (shouldResizeMedia(media, imageLimit)) {
downsize(media, imageLimit) downsize(media, imageLimit)
} else media } else media
} }
.switchMap { upload(it, videoLimit, imageLimit) } .switchMap { upload(it) }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
} }
@ -94,7 +94,7 @@ class MediaUploaderImpl(
} }
} }
override fun prepareMedia(inUri: Uri, videoLimit: Int, imageLimit: Int, filename: String?): Single<PreparedMedia> { override fun prepareMedia(inUri: Uri, videoLimit: Long, imageLimit: Long, filename: String?): Single<PreparedMedia> {
return Single.fromCallable { return Single.fromCallable {
var mediaSize = getMediaSize(contentResolver, inUri) var mediaSize = getMediaSize(contentResolver, inUri)
var uri = inUri var uri = inUri
@ -159,7 +159,7 @@ class MediaUploaderImpl(
private val contentResolver = context.contentResolver private val contentResolver = context.contentResolver
private fun upload(media: QueuedMedia, videoLimit: Int, imageLimit: Int): Observable<UploadEvent> { private fun upload(media: QueuedMedia): Observable<UploadEvent> {
return Observable.create { emitter -> return Observable.create { emitter ->
var (mimeType, fileExtension) = getMimeTypeAndSuffixFromFilenameOrUri(media.uri, media.originalFileName) var (mimeType, fileExtension) = getMimeTypeAndSuffixFromFilenameOrUri(media.uri, media.originalFileName)
val filename = String.format("%s_%s_%s%s", val filename = String.format("%s_%s_%s%s",
@ -196,13 +196,13 @@ class MediaUploaderImpl(
} }
} }
private fun downsize(media: QueuedMedia, imageLimit: Int): QueuedMedia { private fun downsize(media: QueuedMedia, imageLimit: Long): QueuedMedia {
val file = createNewImageFile(context) val file = createNewImageFile(context)
DownsizeImageTask.resize(arrayOf(media.uri), imageLimit, context.contentResolver, file) DownsizeImageTask.resize(arrayOf(media.uri), imageLimit, context.contentResolver, file)
return media.copy(uri = file.toUri(), mediaSize = file.length()) return media.copy(uri = file.toUri(), mediaSize = file.length())
} }
private fun shouldResizeMedia(media: QueuedMedia, imageLimit: Int): Boolean { private fun shouldResizeMedia(media: QueuedMedia, imageLimit: Long): Boolean {
// resize only images // resize only images
if(media.type == QueuedMedia.Type.IMAGE) { if(media.type == QueuedMedia.Type.IMAGE) {
// resize when exceed image limit // resize when exceed image limit

View File

@ -25,7 +25,7 @@ data class NodeInfoLink(
) )
data class NodeInfoLinks( data class NodeInfoLinks(
val links: ArrayList<NodeInfoLink> val links: List<NodeInfoLink>
) )
// we care only about supported postFormats // we care only about supported postFormats
@ -41,14 +41,14 @@ data class NodeInfoSoftware(
) )
data class NodeInfoPleromaUploadLimits( data class NodeInfoPleromaUploadLimits(
val avatar: Int?, val avatar: Long?,
val background: Int?, val background: Long?,
val banner: Int?, val banner: Long?,
val general: Int? val general: Long?
) )
data class NodeInfoPixelfedUploadLimits( data class NodeInfoPixelfedUploadLimits(
@SerializedName("max_photo_size") val maxPhotoSize: Int? @SerializedName("max_photo_size") val maxPhotoSize: Long?
) )
data class NodeInfoPixelfedConfig( data class NodeInfoPixelfedConfig(
@ -56,7 +56,7 @@ data class NodeInfoPixelfedConfig(
) )
data class NodeInfoMetadata( data class NodeInfoMetadata(
val postFormats: ArrayList<String>?, val postFormats: List<String>?,
val uploadLimits: NodeInfoPleromaUploadLimits?, val uploadLimits: NodeInfoPleromaUploadLimits?,
val config: NodeInfoPixelfedConfig? val config: NodeInfoPixelfedConfig?
) )

View File

@ -41,6 +41,7 @@ import org.mockito.Mockito.mock
import org.robolectric.Robolectric import org.robolectric.Robolectric
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
import org.robolectric.fakes.RoboMenuItem import org.robolectric.fakes.RoboMenuItem
import java.lang.Math.pow
/** /**
* Created by charlag on 3/7/18. * Created by charlag on 3/7/18.
@ -75,9 +76,7 @@ class ComposeActivityTest {
notificationLight = true notificationLight = true
) )
var instanceResponseCallback: (()->Instance)? = null var instanceResponseCallback: (()->Instance)? = null
private val nodeinfoLinks = NodeInfoLinks( var nodeinfoResponseCallback: (()->NodeInfo)? = null
links = arrayListOf<NodeInfoLink>()
)
@Before @Before
fun setupActivity() { fun setupActivity() {
@ -89,7 +88,27 @@ class ComposeActivityTest {
apiMock = mock(MastodonApi::class.java) apiMock = mock(MastodonApi::class.java)
`when`(apiMock.getCustomEmojis()).thenReturn(Single.just(emptyList())) `when`(apiMock.getCustomEmojis()).thenReturn(Single.just(emptyList()))
`when`(apiMock.getNodeinfoLinks()).thenReturn(Single.just(nodeinfoLinks)) `when`(apiMock.getNodeinfoLinks()).thenReturn(object: Single<NodeInfoLinks>() {
override fun subscribeActual(observer: SingleObserver<in NodeInfoLinks>) {
if (nodeinfoResponseCallback == null) {
observer.onError(Throwable())
} else {
observer.onSuccess(NodeInfoLinks(
listOf( NodeInfoLink( "", "" ) )
))
}
}
})
`when`(apiMock.getNodeinfo("")).thenReturn(object: Single<NodeInfo>() {
override fun subscribeActual(observer: SingleObserver< in NodeInfo>) {
val nodeinfo = nodeinfoResponseCallback?.invoke()
if (nodeinfo == null) {
observer.onError(Throwable())
} else {
observer.onSuccess(nodeinfo)
}
}
})
`when`(apiMock.getInstance()).thenReturn(object: Single<Instance>() { `when`(apiMock.getInstance()).thenReturn(object: Single<Instance>() {
override fun subscribeActual(observer: SingleObserver<in Instance>) { override fun subscribeActual(observer: SingleObserver<in Instance>) {
val instance = instanceResponseCallback?.invoke() val instance = instanceResponseCallback?.invoke()
@ -164,12 +183,66 @@ class ComposeActivityTest {
@Test @Test
fun whenMaximumTootCharsIsPopulated_customLimitIsUsed() { fun whenMaximumTootCharsIsPopulated_customLimitIsUsed() {
val customMaximum = 1000 val customMaximum = 2147483647
instanceResponseCallback = { getInstanceWithMaximumTootCharacters(customMaximum) } instanceResponseCallback = { getInstanceWithMaximumTootCharacters(customMaximum) }
setupActivity() setupActivity()
assertEquals(customMaximum, activity.maximumTootCharacters) assertEquals(customMaximum, activity.maximumTootCharacters)
} }
@Test
fun whenPleromaInNodeinfo_attachmentLimitsRemoved() {
nodeinfoResponseCallback = { getPleromaNodeinfo(
null,
NodeInfoPleromaUploadLimits( 100, 100, 100, 100 ))
}
setupActivity()
assertEquals(true, activity.viewModel.hasNoAttachmentLimits)
}
@Test
fun whenPleromaInNodeinfo_haveFormatting() {
nodeinfoResponseCallback = { getPleromaNodeinfo(
listOf("text/plain", "text/markdown", "text/bbcode"),
NodeInfoPleromaUploadLimits( 100, 100, 100, 100 ))
}
setupActivity()
assertArrayEquals(arrayOf("text/markdown", "text/bbcode"), activity.supportedFormattingSyntax.toTypedArray())
}
@Test
fun whenPleromaInNodeinfo_haveCustomUploadLimits() {
nodeinfoResponseCallback = { getPleromaNodeinfo(
null,
NodeInfoPleromaUploadLimits( Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE, Long.MAX_VALUE ))
}
setupActivity()
assertEquals(Long.MAX_VALUE, activity.viewModel.instanceMetadata.value!!.imageLimit)
assertEquals(Long.MAX_VALUE, activity.viewModel.instanceMetadata.value!!.videoLimit)
}
@Test
fun whenPixelfedInNodeInfo_haveCustomUploadLimits() {
nodeinfoResponseCallback = { getPixelfedNodeinfo( 1024 * 1024 ) }
setupActivity()
assertEquals(1024 * 1024 * 1024, activity.viewModel.instanceMetadata.value!!.imageLimit)
assertEquals(1024 * 1024 * 1024, activity.viewModel.instanceMetadata.value!!.videoLimit)
assertArrayEquals(emptyArray(), activity.supportedFormattingSyntax.toTypedArray()) // pixelfed has no formatting
}
@Test
fun whenMastodonInNodeinfo_butItsAGlitch() {
nodeinfoResponseCallback = { getMastodonNodeinfo( "3.1.0+glitch" ) }
setupActivity()
assertArrayEquals(arrayOf("text/markdown", "text/html"), activity.supportedFormattingSyntax.toTypedArray())
}
@Test
fun whenMastodonInNodeinfo_butItsBoringVanilla() {
nodeinfoResponseCallback = { getMastodonNodeinfo( "3.1.0" ) }
setupActivity()
assertArrayEquals(emptyArray(), activity.supportedFormattingSyntax.toTypedArray())
}
@Test @Test
fun whenTextContainsNoUrl_everyCharacterIsCounted() { fun whenTextContainsNoUrl_everyCharacterIsCounted() {
val content = "This is test content please ignore thx " val content = "This is test content please ignore thx "
@ -361,6 +434,43 @@ class ComposeActivityTest {
activity.findViewById<EditText>(R.id.composeEditField).setText(text ?: "Some text") activity.findViewById<EditText>(R.id.composeEditField).setText(text ?: "Some text")
} }
private fun getPleromaNodeinfo(postFormats: List<String>?, limits: NodeInfoPleromaUploadLimits) : NodeInfo
{
return NodeInfo(
NodeInfoMetadata(
postFormats,
limits,
null
),
NodeInfoSoftware(
"pleroma",
"2.0.0"
)
)
}
private fun getPixelfedNodeinfo(maxPhotoSize: Long) : NodeInfo {
return NodeInfo(
NodeInfoMetadata(
null, null, NodeInfoPixelfedConfig( NodeInfoPixelfedUploadLimits( maxPhotoSize ) )
),
NodeInfoSoftware(
"pixelfed",
"2.0.0"
)
)
}
private fun getMastodonNodeinfo(version: String) : NodeInfo {
return NodeInfo(
null,
NodeInfoSoftware(
"mastodon",
version
)
)
}
private fun getInstanceWithMaximumTootCharacters(maximumTootCharacters: Int?): Instance private fun getInstanceWithMaximumTootCharacters(maximumTootCharacters: Int?): Instance
{ {
return Instance( return Instance(