migrate orion repo into monorepo structure
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
package particle_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/flowy-live/llink/internal/network"
|
||||
"github.com/flowy-live/llink/internal/particle"
|
||||
"github.com/flowy-live/llink/internal/testhelper"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var dbPool *pgxpool.Pool
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
dbPool = testhelper.SetupTestDB()
|
||||
defer testhelper.TeardownTestDB()
|
||||
|
||||
ret := m.Run()
|
||||
os.Exit(ret)
|
||||
}
|
||||
|
||||
func getStreamStatus(data json.RawMessage) string {
|
||||
var d struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
json.Unmarshal(data, &d)
|
||||
return d.Status
|
||||
}
|
||||
|
||||
func TestParticleService_CreateAndGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network first
|
||||
net, err := networkSvc.Create(ctx, "Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test Create stream particle
|
||||
data := json.RawMessage(`{"name":"My Stream","status":"open","description":"A test stream"}`)
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Data: data,
|
||||
}
|
||||
created, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, created.ID)
|
||||
assert.Equal(t, particle.TypeStream, created.Type)
|
||||
assert.Equal(t, net.ID, created.NetworkID)
|
||||
assert.Nil(t, created.ParentID)
|
||||
assert.Equal(t, particle.VisibilityNetworkAll, created.Visibility)
|
||||
assert.Equal(t, string(particle.StreamStatusOpen), getStreamStatus(created.Data))
|
||||
|
||||
// Test GetByID
|
||||
found, err := svc.GetByID(ctx, created.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, created.ID, found.ID)
|
||||
|
||||
// Test GetByID with non-existent id
|
||||
_, err = svc.GetByID(ctx, "particle_nonexistent", "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrNotFound)
|
||||
|
||||
// Note: Network membership check is handler's responsibility
|
||||
// Service assumes caller is already verified as network member
|
||||
}
|
||||
|
||||
func TestParticleService_StreamCapacity(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network with capacity 2
|
||||
net, err := networkSvc.Create(ctx, "Capacity Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = networkSvc.SetOpenStreamCapacity(ctx, net.ID, 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create first stream - should succeed
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"name":"Stream 1","status":"open"}`),
|
||||
}
|
||||
stream1, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create second stream - should succeed
|
||||
input.Data = json.RawMessage(`{"name":"Stream 2","status":"open"}`)
|
||||
stream2, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create third stream - should fail with capacity exceeded
|
||||
input.Data = json.RawMessage(`{"name":"Stream 3","status":"open"}`)
|
||||
_, err = svc.Create(ctx, input, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrCapacityExceeded)
|
||||
|
||||
// Close a stream
|
||||
err = svc.CloseStream(ctx, stream1.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Now we can create another stream
|
||||
stream3, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, stream3.ID)
|
||||
|
||||
// Verify stream2 is still open
|
||||
found, err := svc.GetByID(ctx, stream2.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(particle.StreamStatusOpen), getStreamStatus(found.Data))
|
||||
|
||||
// Verify stream1 is closed
|
||||
found, err = svc.GetByID(ctx, stream1.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(particle.StreamStatusClosed), getStreamStatus(found.Data))
|
||||
}
|
||||
|
||||
func TestParticleService_NestedParticles(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network
|
||||
net, err := networkSvc.Create(ctx, "Nested Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a parent stream
|
||||
streamInput := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"name":"Parent Stream","status":"open"}`),
|
||||
}
|
||||
stream, err := svc.Create(ctx, streamInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a text particle as child
|
||||
textInput := particle.CreateInput{
|
||||
Type: particle.TypeText,
|
||||
NetworkID: net.ID,
|
||||
ParentID: &stream.ID,
|
||||
Data: json.RawMessage(`{"content":"Hello world"}`),
|
||||
}
|
||||
text, err := svc.Create(ctx, textInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, stream.ID, *text.ParentID)
|
||||
|
||||
// Create a file as child of stream
|
||||
fileInput := particle.CreateInput{
|
||||
Type: particle.TypeFile,
|
||||
NetworkID: net.ID,
|
||||
ParentID: &stream.ID,
|
||||
Data: json.RawMessage(`{"object_id":"obj_abc123","filename":"test.pdf","mime_type":"application/pdf","size":1024}`),
|
||||
}
|
||||
file, err := svc.Create(ctx, fileInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, stream.ID, *file.ParentID)
|
||||
|
||||
// List children of stream
|
||||
children, err := svc.List(ctx, net.ID, &stream.ID, "[email protected]", particle.ListFilter{}, nil, 50)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, children.Particles, 2)
|
||||
}
|
||||
|
||||
func TestParticleService_CustomVisibility(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network with a member
|
||||
net, err := networkSvc.Create(ctx, "Visibility Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = networkSvc.AddMembers(ctx, net.ID, []string{"[email protected]", "[email protected]"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a stream with custom visibility including only admin and member
|
||||
streamInput := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Visibility: particle.VisibilityCustom,
|
||||
Members: []string{"[email protected]", "[email protected]"},
|
||||
Data: json.RawMessage(`{"name":"Private Stream","status":"open"}`),
|
||||
}
|
||||
stream, err := svc.Create(ctx, streamInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Admin can access
|
||||
_, err = svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Member can access
|
||||
_, err = svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Other network member cannot access
|
||||
_, err = svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrAccessDenied)
|
||||
|
||||
// Non-network member cannot access
|
||||
_, err = svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrAccessDenied)
|
||||
}
|
||||
|
||||
func TestParticleService_UpdateAndDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network
|
||||
net, err := networkSvc.Create(ctx, "Update Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a text particle
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeText,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"content":"Original content"}`),
|
||||
}
|
||||
created, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Update the particle
|
||||
newData := json.RawMessage(`{"content":"Updated content"}`)
|
||||
updated, err := svc.Update(ctx, created.ID, newData, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
// PostgreSQL normalizes JSON, so compare unmarshaled values
|
||||
var expected, actual map[string]interface{}
|
||||
json.Unmarshal(newData, &expected)
|
||||
json.Unmarshal(updated.Data, &actual)
|
||||
assert.Equal(t, expected, actual)
|
||||
|
||||
// Delete the particle
|
||||
err = svc.Delete(ctx, created.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify it's gone
|
||||
_, err = svc.GetByID(ctx, created.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrNotFound)
|
||||
}
|
||||
|
||||
func TestParticleService_ListRootParticles(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network
|
||||
net, err := networkSvc.Create(ctx, "List Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create multiple root particles
|
||||
for i := 0; i < 3; i++ {
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"name":"Stream","status":"open"}`),
|
||||
}
|
||||
_, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// List root particles (parentID = nil)
|
||||
list, err := svc.List(ctx, net.ID, nil, "[email protected]", particle.ListFilter{}, nil, 50)
|
||||
assert.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(list.Particles), 3)
|
||||
}
|
||||
|
||||
func TestParticleService_OpenCloseStream(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network
|
||||
net, err := networkSvc.Create(ctx, "Open Close Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a stream
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"name":"Test Stream","status":"open"}`),
|
||||
}
|
||||
stream, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(particle.StreamStatusOpen), getStreamStatus(stream.Data))
|
||||
|
||||
// Close the stream
|
||||
err = svc.CloseStream(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify it's closed
|
||||
found, err := svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(particle.StreamStatusClosed), getStreamStatus(found.Data))
|
||||
|
||||
// Try to close again - should error
|
||||
err = svc.CloseStream(ctx, stream.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrStreamAlreadyClosed)
|
||||
|
||||
// Reopen the stream
|
||||
err = svc.OpenStream(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify it's open
|
||||
found, err = svc.GetByID(ctx, stream.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(particle.StreamStatusOpen), getStreamStatus(found.Data))
|
||||
|
||||
// Try to open again - should error
|
||||
err = svc.OpenStream(ctx, stream.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrStreamAlreadyOpen)
|
||||
}
|
||||
|
||||
func TestParticleService_NotAStream(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network
|
||||
net, err := networkSvc.Create(ctx, "Not Stream Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a text particle
|
||||
input := particle.CreateInput{
|
||||
Type: particle.TypeText,
|
||||
NetworkID: net.ID,
|
||||
Data: json.RawMessage(`{"content":"Hello"}`),
|
||||
}
|
||||
text, err := svc.Create(ctx, input, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Try to open it as a stream
|
||||
err = svc.OpenStream(ctx, text.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrNotAStream)
|
||||
|
||||
// Try to close it as a stream
|
||||
err = svc.CloseStream(ctx, text.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrNotAStream)
|
||||
}
|
||||
|
||||
func TestParticleService_AccessInheritance(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
networkSvc := network.NewService(dbPool)
|
||||
svc := particle.NewService(dbPool, networkSvc)
|
||||
|
||||
// Create a network with members
|
||||
net, err := networkSvc.Create(ctx, "Access Inheritance Test Network", "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = networkSvc.AddMembers(ctx, net.ID, []string{"[email protected]", "[email protected]"})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a stream with custom visibility (admin and member only)
|
||||
streamInput := particle.CreateInput{
|
||||
Type: particle.TypeStream,
|
||||
NetworkID: net.ID,
|
||||
Visibility: particle.VisibilityCustom,
|
||||
Members: []string{"[email protected]", "[email protected]"},
|
||||
Data: json.RawMessage(`{"name":"Private Stream","status":"open"}`),
|
||||
}
|
||||
stream, err := svc.Create(ctx, streamInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create a child text (network_all visibility)
|
||||
textInput := particle.CreateInput{
|
||||
Type: particle.TypeText,
|
||||
NetworkID: net.ID,
|
||||
ParentID: &stream.ID,
|
||||
Data: json.RawMessage(`{"content":"Child text"}`),
|
||||
}
|
||||
text, err := svc.Create(ctx, textInput, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Admin can access child
|
||||
_, err = svc.GetByID(ctx, text.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Member can access child
|
||||
_, err = svc.GetByID(ctx, text.ID, "[email protected]")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Other cannot access child (even though child is network_all, parent restricts)
|
||||
_, err = svc.GetByID(ctx, text.ID, "[email protected]")
|
||||
assert.Error(t, err)
|
||||
assert.ErrorIs(t, err, particle.ErrAccessDenied)
|
||||
}
|
||||
Reference in New Issue
Block a user