Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ kotlin-ide/
.idea/
.aider*
.env
.idea/misc.xml
.vscode/
2 changes: 2 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -53,68 +53,70 @@ import java.net.URL
import java.net.URLConnection
import java.net.URLDecoder

//Use this to encode files not just images
//Needs to be tested sometime
//Can I modify this so that Http transfer does the majority of the encoding?
class FileEncoder {//Made by Craig. Encodes via base64.
class FileEncoder {

@OptIn(ExperimentalEncodingApi::class)
fun encodebase64(ctxt: Context, inputuri: Uri): String?{
try {
fun encodebase64(ctxt: Context, inputuri: Uri): String? {
return try {
val encodedstrm: InputStream? = ctxt.contentResolver.openInputStream(inputuri)
val bytes = encodedstrm?.readBytes()
encodedstrm?.close()
return if (bytes != null) {
Base64.encode(bytes)
} else {
"Cannot encode file"
}
} catch(e: Exception){
encodeBytesBase64(bytes)
} catch (e: Exception) {
e.printStackTrace()
return "Cannot encode file"}
"Cannot encode file"
}
}

@OptIn(ExperimentalEncodingApi::class)
internal fun encodeBytesBase64(bytes: ByteArray?): String? {
return if (bytes != null) {
Base64.encode(bytes)
} else {
"Cannot encode file"
}
}

@OptIn(ExperimentalEncodingApi::class)//Made by Craig
fun decodeBase64(inputbase64:String, output: File): File{//Decodes to a file. Uses base64
@OptIn(ExperimentalEncodingApi::class)
fun decodeBase64(inputbase64: String, output: File): File {
val decodedfilebytes = Base64.decode(inputbase64)
val decodedstrm = FileOutputStream(output)
decodedstrm.write(decodedfilebytes)
decodedstrm.close()
return output
}
fun sendImage(imageURI: Uri?, tgtaddress: InetAddress, tgtport:Int, appctxt: Context): Boolean{//Testing sending images
try{//we can utilize this if we opt not to use JSON
if(imageURI != null){
val fp = encodebase64(appctxt, imageURI)//encodes file to base64
if(!fp.equals("Cannot encode file")) {
val efp = URLEncoder.encode(fp, "UTF-8")//ensures that the file URI is utf-8 encoded
val connection =
URL("http://${tgtaddress.hostAddress}:${tgtport}/upload?file=$efp").openConnection() as HttpURLConnection
val request = "POST"//Specifies the request as a POST
connection.doOutput = true
connection.requestMethod = request
connection.setChunkedStreamingMode(0)
val instream = appctxt.contentResolver.openInputStream(imageURI)
val outstream = connection.outputStream
val readingbuffer = ByteArray(1024)
var finishedreading: Int
while (instream?.read(readingbuffer).also { finishedreading = it!! } != -1) {
outstream.write(readingbuffer, 0, finishedreading)
}
outstream.close()
instream?.close()

fun sendImage(imageURI: Uri?, tgtaddress: InetAddress, tgtport: Int, appctxt: Context): Boolean {
try {
if (imageURI != null) {
val fp = encodebase64(appctxt, imageURI)
if (!fp.equals("Cannot encode file")) {
val efp = URLEncoder.encode(fp, "UTF-8")
val connection =
URL("http://${tgtaddress.hostAddress}:${tgtport}/upload?file=$efp").openConnection() as HttpURLConnection
val request = "POST"
connection.doOutput = true
connection.requestMethod = request
connection.setChunkedStreamingMode(0)
val instream = appctxt.contentResolver.openInputStream(imageURI)
val outstream = connection.outputStream
val readingbuffer = ByteArray(1024)
var finishedreading: Int
while (instream?.read(readingbuffer).also { finishedreading = it!! } != -1) {
outstream.write(readingbuffer, 0, finishedreading)
}
outstream.close()
instream?.close()
} else {
return false
}
} else {
return false
}} else {
return false
}
}
catch(e: Exception){
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,52 @@ import android.util.Log
*/

object Logger {
internal const val TAG_PREFIX = "MeshChat_"
private const val LOGGING_ENABLED = true
private const val TAG_PREFIX = "MeshChat_"

internal fun buildTag(tag: String): String {
return "$TAG_PREFIX$tag"
}

internal fun buildCriticalTag(tag: String): String {
return "${TAG_PREFIX}${tag}_CRITICAL"
}

fun d(tag: String, message: String) {
if (LOGGING_ENABLED) {
Log.d("$TAG_PREFIX$tag", message)
Log.d(buildTag(tag), message)
}
}

fun i(tag: String, message: String) {
if (LOGGING_ENABLED) {
Log.i("$TAG_PREFIX$tag", message)
Log.i(buildTag(tag), message)
}
}

fun w(tag: String, message: String) {
if (LOGGING_ENABLED) {
Log.w("$TAG_PREFIX$tag", message)
Log.w(buildTag(tag), message)
}
}

fun e(tag: String, message: String, throwable: Throwable? = null) {
if (LOGGING_ENABLED) {
if (throwable != null) {
Log.e("$TAG_PREFIX$tag", message, throwable)
Log.e(buildTag(tag), message, throwable)
} else {
Log.e("$TAG_PREFIX$tag", message)
Log.e(buildTag(tag), message)
}
}
}

// Log important events that should be visible even in production
fun critical(tag: String, message: String, throwable: Throwable? = null) {
val criticalTag = buildCriticalTag(tag)
if (throwable != null) {
Log.e("$TAG_PREFIX${tag}_CRITICAL", message, throwable)
Log.e(criticalTag, message, throwable)
} else {
Log.e("$TAG_PREFIX${tag}_CRITICAL", message)
Log.e(criticalTag, message)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class MessageMigrationUtils(
}
}

private fun createConversationId(uuid1: String, uuid2: String): String {
internal fun createConversationId(uuid1: String, uuid2: String): String {
// Special cases for test devices
if (uuid2 == "test-device-uuid") {
return "local-user-test-device-uuid"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ class InputStreamCounter(
}
}

override fun read(b: ByteArray): Int {
return super.read(b).also {
if(it != -1)
bytesRead += it
}
}

override fun read(b: ByteArray, off: Int, len: Int): Int {
return super.read(b, off, len).also {
if(it != -1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.greybox.projectmesh.components

import org.junit.Assert.*
import org.junit.Test
import java.lang.reflect.Field
import com.ustadmobile.meshrabiya.vnet.wifi.WifiConnectConfig

/**
* JVM-only tests for simple data + status in WifiConnection.kt.
* No Android/Compose/Mockito required.
*
* NOTE: We allocate a dummy WifiConnectConfig via Unsafe and NEVER call its methods.
* We also avoid ConnectRequest.equals()/hashCode() because that would call
* WifiConnectConfig.hashCode() internally (which can NPE if fields are null).
*/
class WifiConnectionTest {

// -----------------------------
// Enum: stages must exist
// -----------------------------
@Test
fun checkAllStatusesExist() {
val all = ConnectWifiLauncherStatus.values().toSet()
assertTrue(ConnectWifiLauncherStatus.INACTIVE in all)
assertTrue(ConnectWifiLauncherStatus.REQUESTING_PERMISSION in all)
assertTrue(ConnectWifiLauncherStatus.LOOKING_FOR_NETWORK in all)
assertTrue(ConnectWifiLauncherStatus.REQUESTING_LINK in all)
assertEquals(4, all.size)
}

// -----------------------------------------------
// Result model: failure shape must look correct
// -----------------------------------------------
@Test
fun checkFailureResultLooksRight() {
val error = Exception("expected failure")
val result = ConnectWifiLauncherResult(
hotspotConfig = null,
exception = error,
isWifiConnected = false
)
assertFalse(result.isWifiConnected)
assertNull(result.hotspotConfig)
assertNotNull(result.exception)
assertEquals("expected failure", result.exception?.message)
}

// -------------------------------------------------------
// Result model: data-class copy/equals/hashCode sanity
// (safe because hotspotConfig = null)
// -------------------------------------------------------
@Test
fun checkResultCopiesAndComparesCorrectly() {
val first = ConnectWifiLauncherResult(
hotspotConfig = null,
exception = Exception("boom"),
isWifiConnected = false
)
val same = first.copy()
val different = first.copy(exception = Exception("other"))

assertEquals(first, same)
assertEquals(first.hashCode(), same.hashCode())
assertNotEquals(first, different)
}

// =========================================
// ConnectRequest: JVM tests (no Mockito)
// Avoid equals()/hashCode() on the whole object.
// =========================================

@Test
fun connectRequest_defaultTimeIsZero() {
val cfg = unsafeInstance<WifiConnectConfig>()
val req = ConnectRequest(connectConfig = cfg)
assertEquals(0L, req.receivedTime)
assertSame(cfg, req.connectConfig) // same reference
}

@Test
fun connectRequest_customTimePreserved() {
val cfg = unsafeInstance<WifiConnectConfig>()
val req = ConnectRequest(receivedTime = 123456789L, connectConfig = cfg)
assertEquals(123456789L, req.receivedTime)
assertSame(cfg, req.connectConfig)
}

@Test
fun connectRequest_copyPreservesConfigAndChangesTime() {
val cfg = unsafeInstance<WifiConnectConfig>()
val original = ConnectRequest(receivedTime = 100L, connectConfig = cfg)
val copiedSame = original.copy()
val copiedChanged = original.copy(receivedTime = 200L)

// field-wise checks (no equals()/hashCode())
assertEquals(100L, copiedSame.receivedTime)
assertSame(cfg, copiedSame.connectConfig)

assertEquals(200L, copiedChanged.receivedTime)
assertSame(cfg, copiedChanged.connectConfig)

// sanity: copies are distinct instances
assertNotSame(original, copiedSame)
assertNotSame(original, copiedChanged)
}

// -------------------------------------------------------
// Tiny Unsafe helper for constructor-less allocation
// -------------------------------------------------------
@Suppress("UNCHECKED_CAST")
private inline fun <reified T> unsafeInstance(): T {
val unsafe = getUnsafe()
val allocate = unsafe.javaClass.getMethod("allocateInstance", Class::class.java)
return allocate.invoke(unsafe, T::class.java) as T
}

private fun getUnsafe(): Any {
val clazz = try {
Class.forName("sun.misc.Unsafe")
} catch (_: ClassNotFoundException) {
Class.forName("jdk.internal.misc.Unsafe")
}
val f: Field = clazz.getDeclaredField("theUnsafe")
f.isAccessible = true
return f.get(null)
}
}
54 changes: 54 additions & 0 deletions app/src/test/java/com/greybox/projectmesh/db/MeshDatabaseTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.greybox.projectmesh.db

import org.junit.Assert.*
import org.junit.Test
import java.lang.reflect.Modifier
import com.greybox.projectmesh.messaging.data.dao.MessageDao
import com.greybox.projectmesh.messaging.data.dao.ConversationDao
import com.greybox.projectmesh.user.UserDao
import androidx.room.RoomDatabase

/**
* JVM-only tests for MeshDatabase class shape.
* These do NOT spin up Room or touch Android runtime.
*
* We verify:
* - MeshDatabase is abstract
* - MeshDatabase extends RoomDatabase
* - Required DAO methods exist and have the correct return types
*
* Anything involving entities, queries, schema, and migrations belongs
* in src/androidTest with an in-memory RoomDatabase.
*/
class MeshDatabaseTest {

@Test
fun meshDatabase_isAbstract_and_extendsRoomDatabase() {
val cls = MeshDatabase::class.java

// Must be abstract
assertTrue("MeshDatabase must be abstract",
Modifier.isAbstract(cls.modifiers))

// Must extend androidx.room.RoomDatabase
assertTrue("MeshDatabase must extend RoomDatabase",
RoomDatabase::class.java.isAssignableFrom(cls))
}

@Test
fun meshDatabase_has_requiredDaoMethods_with_correctReturnTypes() {
val cls = MeshDatabase::class.java

// messageDao(): MessageDao
val messageDaoMethod = cls.getMethod("messageDao")
assertEquals(MessageDao::class.java, messageDaoMethod.returnType)

// userDao(): UserDao
val userDaoMethod = cls.getMethod("userDao")
assertEquals(UserDao::class.java, userDaoMethod.returnType)

// conversationDao(): ConversationDao
val conversationDaoMethod = cls.getMethod("conversationDao")
assertEquals(ConversationDao::class.java, conversationDaoMethod.returnType)
}
}
Loading
Loading