Browse Source

Working alias generation, inserts into DB, some integration tests.

Frederic G. MARAND 10 năm trước cách đây
mục cha
commit
ecf5044df5
12 tập tin đã thay đổi với 195 bổ sung20 xóa
  1. 3 0
      .gitignore
  2. 35 3
      README.md
  3. 3 3
      doc/model.dot
  4. 2 2
      doc/model.svg
  5. 4 0
      storage/info.go
  6. 4 1
      storage/user.go
  7. 2 2
      strategy/hexcrc32.go
  8. 1 1
      strategy/manual.go
  9. 32 8
      strategy/strategy.go
  10. 97 0
      strategy/strategy_test.go
  11. 11 0
      url/longurl.go
  12. 1 0
      url/shorturl.go

+ 3 - 0
.gitignore

@@ -6,6 +6,9 @@
 *.a
 *.so
 
+# Coverage results
+coverage.out
+
 # Folders
 _obj
 _test

+ 35 - 3
README.md

@@ -1,15 +1,47 @@
+
+     #    #  #    #  #####   ######
+     #   #   #    #  #    #      #
+     ####    #    #  #    #     #
+     #  #    #    #  #####     #
+     #   #   #    #  #   #    #
+     #    #   ####   #    #  ######
+
 _Kurz_ is yet another URL shortener/aliaser.
 
-  * built as a Go API, for internal site use as well as public use
-  * web front end provided
+  * built as a Go data model API, for internal site use as well as public use
+  * web front end separate from data logic
   * multiple aliasing strategies. provided:
+    * identity
     * 32-bits hexa string, leading 0s omitted
-    * base36
     * manual selection
+    * easy to extend, by registering additional AliasStrategy objects
   * supporting
     * vanity domains
     * relative paths (API only, no Web UI), for use within web sites
     * "slug"-type multipart aliasing for SEO
   * usage statistics
+  * some tests available
 
 It is currently available under the General Public License version 3 or later.
+
+
+Using Kurz
+==========
+
+The main Kurz command show how to initialize the package and access statistics.
+
+You can find usage examples in strategy_test.go:
+
+- TestBaseAlias() shows how to generate aliases
+- TestUseCounts() show how to query strategy statistics
+
+
+Running tests
+=============
+
+Currently, only the strategy package has tests, and these need some setup, as
+they touch the database:
+
+- either redefine the credentials in strategy_test.go#initTestStorage()
+- or create a database with privileges appropriate for them
+- or send a PR submitting a better DB initialization mechanism for tests

+ 3 - 3
doc/model.dot

@@ -9,7 +9,7 @@ digraph kurz {
   language [ label="<k>Language|native name" ];
   likings [ label="<k>Likings|<user>user|<long>longUrl|ts" ];
   long [ label="<k>Long URL|value" ];
-  longmeta [ label="<k>Long metadata|<long>longUrl|mimeType|<language>language|imagePath|<info>origin" ];
+  longmeta [ label="<k>Long metadata|<url>url|mimeType|<language>language|imagePath|<info>origin" ];
   session [ label="<k>Session|<user>user|<info>info" ];
   short [ label="<k>Short URL|value|<long>shortFor|<domain>domain|<strategy>strategy|<user>submittedBy|submittedOn|isEnabled" ];
   strategy [ label="<k>Strategy|name" ];
@@ -27,10 +27,10 @@ digraph kurz {
 
   longmeta:info -> eventinfo:k;
   longmeta:language -> language:k;
-  longmeta:long -> long:k;
+  longmeta:url -> long:k;
 
   session:info -> eventinfo:k;
-  session:user -> user:k;  
+  session:user -> user:k;
 
   short:domain -> domain:k;
   short:long -> long:k;

+ 2 - 2
doc/model.svg

@@ -137,7 +137,7 @@
 <polygon fill="none" stroke="black" points="282.5,-52 282.5,-208 403.5,-208 403.5,-52 282.5,-52"/>
 <text text-anchor="middle" x="343" y="-191.4" font-family="Times Roman,serif" font-size="14.00">Long metadata</text>
 <polyline fill="none" stroke="black" points="282.5,-182 403.5,-182 "/>
-<text text-anchor="middle" x="343" y="-165.4" font-family="Times Roman,serif" font-size="14.00">longUrl</text>
+<text text-anchor="middle" x="343" y="-165.4" font-family="Times Roman,serif" font-size="14.00">url</text>
 <polyline fill="none" stroke="black" points="282.5,-156 403.5,-156 "/>
 <text text-anchor="middle" x="343" y="-139.4" font-family="Times Roman,serif" font-size="14.00">mimeType</text>
 <polyline fill="none" stroke="black" points="282.5,-130 403.5,-130 "/>
@@ -160,7 +160,7 @@
 <text text-anchor="middle" x="527" y="-183.4" font-family="Times Roman,serif" font-size="14.00">is a</text>
 </g>
 <!-- longmeta&#45;&gt;long -->
-<g id="edge18" class="edge"><title>longmeta:long&#45;&gt;long:k</title>
+<g id="edge18" class="edge"><title>longmeta:url&#45;&gt;long:k</title>
 <path fill="none" stroke="red" d="M404,-169C425.465,-169 452.6,-226.422 475.483,-240.777"/>
 <polygon fill="red" stroke="red" points="474.406,-244.108 485,-244 476.651,-237.478 474.406,-244.108"/>
 <text text-anchor="middle" x="434" y="-212.4" font-family="Times Roman,serif" font-size="14.00">is a</text>

+ 4 - 0
storage/info.go

@@ -0,0 +1,4 @@
+package storage
+
+type EventInfo struct {
+}

+ 4 - 1
storage/user.go

@@ -1,6 +1,9 @@
 package storage
 
-type User struct{}
+type User struct {
+	Id              int64
+	DefaultStrategy string // The name of the user's default strategy
+}
 
 func CurrentUser() User {
 	var ret User

+ 2 - 2
strategy/hexcrc32.go

@@ -19,11 +19,11 @@ type HexCrc32Strategy struct {
 	base baseStrategy
 }
 
-func (s HexCrc32Strategy) Name() string {
+func (y HexCrc32Strategy) Name() string {
 	return "hexCrc32"
 }
 
-func (s HexCrc32Strategy) Alias(short url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
+func (y HexCrc32Strategy) Alias(short url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
 	var ret url.ShortUrl
 
 	return ret, errors.New("HexCrc32 not implemented yet")

+ 1 - 1
strategy/manual.go

@@ -21,7 +21,7 @@ Alias() implements AliasingStrategy.Alias().
 
   - options is expected to be a non empty single string
 */
-func (y ManualStrategy) Alias(long url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
+func (y ManualStrategy) Alias(long url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
 	var ret url.ShortUrl
 	var err error
 	if len(options) != 1 {

+ 32 - 8
strategy/strategy.go

@@ -10,10 +10,10 @@ Files
 package strategy
 
 import (
-	"errors"
 	"github.com/FGM/kurz/storage"
 	"github.com/FGM/kurz/url"
 	"log"
+	"time"
 )
 
 /*
@@ -23,9 +23,9 @@ The options parameter for Alias() MAY be used by some strategies, in which case
 have to define their expectations about it.
 */
 type AliasingStrategy interface {
-	Name() string                                                        // Return the name of the strategy object
-	Alias(url url.LongUrl, options ...interface{}) (url.ShortUrl, error) // Return the short URL (alias) for a given long (source) URL
-	UseCount(storage storage.Storage) int                                // Return the number of short URLs (aliases) using this strategy.
+	Name() string                                                                           // Return the name of the strategy object
+	Alias(url url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) // Return the short URL (alias) for a given long (source) URL
+	UseCount(storage storage.Storage) int                                                   // Return the number of short URLs (aliases) using this strategy.
 }
 
 type baseStrategy struct{}
@@ -34,11 +34,35 @@ func (y baseStrategy) Name() string {
 	return "base"
 }
 
-func (y baseStrategy) Alias(long url.LongUrl, options ...interface{}) (url.ShortUrl, error) {
-	var ret url.ShortUrl
-	var err error = errors.New("Base strategy is abstract")
+func (y baseStrategy) Alias(long url.LongUrl, s storage.Storage, options ...interface{}) (url.ShortUrl, error) {
+	var short url.ShortUrl
+	var err error
 
-	return ret, err
+	/** TODO
+	 * - validate alias is available
+	 */
+	short = url.ShortUrl{
+		Value:       long.Value,
+		ShortFor:    long,
+		Domain:      long.Domain(),
+		Strategy:    y.Name(),
+		SubmittedBy: storage.CurrentUser(),
+		SubmittedOn: time.Now().UTC().Unix(),
+		IsEnabled:   true,
+	}
+
+	sql := `
+		INSERT INTO shorturl(url, domain, strategy, submittedBy, submittedInfo, isEnabled)
+		VALUES (?, ?, ?, ?, ?, ?)
+		`
+	result, err := s.DB.Exec(sql, short.Value, short.Domain, short.Strategy, short.SubmittedBy.Id, short.SubmittedOn, short.IsEnabled)
+	if err != nil {
+		log.Printf("Failed inserting %s: %#V", short.Value, err)
+	} else {
+		short.Id, _ = result.LastInsertId()
+	}
+
+	return short, err
 }
 
 /**

+ 97 - 0
strategy/strategy_test.go

@@ -0,0 +1,97 @@
+package strategy
+
+import (
+	"github.com/FGM/kurz/storage"
+	"github.com/FGM/kurz/url"
+	"testing"
+)
+
+// FIXME stop using constant credentials
+func initTestStorage(t *testing.T) {
+	var DSN = "root:@tcp(localhost:3306)/go_kurz_test"
+	storage.Service.SetDSN(DSN)
+	err := storage.Service.Open()
+	if err != nil {
+		t.Fatalf("Failed opening the test database: %+V", err)
+	}
+}
+
+func TestBaseAlias(t *testing.T) {
+	const BASE = "base"
+
+	account := storage.User{
+		DefaultStrategy: BASE,
+	}
+	strategy := Strategies[account.DefaultStrategy]
+	if strategy.Name() != BASE {
+		t.Fatalf("Strategy: expected %s, got %s", BASE, strategy.Name())
+	}
+
+	initTestStorage(t)
+	defer storage.Service.Close()
+
+	sourceUrl := url.LongUrl{
+		Value: "http://www.example.com",
+	}
+	alias, err := strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+	if alias.ShortFor != sourceUrl {
+		t.Errorf("Aliasing does not point to proper long URL: expected %+V, got %+V", sourceUrl, alias.ShortFor)
+	}
+
+	if alias.Value != sourceUrl.Value {
+		t.Errorf("Aliasing does not build the proper URL: expected %+V, got %+V", sourceUrl.Value, alias.Value)
+	}
+
+	storage.Service.DB.Exec("TRUNCATE shorturl")
+}
+
+func TestUseCounts(t *testing.T) {
+	const BASE = "base"
+
+	account := storage.User{
+		DefaultStrategy: BASE,
+	}
+	strategy := Strategies[account.DefaultStrategy]
+	if strategy.Name() != BASE {
+		t.Fatalf("Strategy: expected %s, got %s", BASE, strategy.Name())
+	}
+
+	initTestStorage(t)
+	defer storage.Service.Close()
+
+	initialCount := strategy.UseCount(storage.Service)
+	if initialCount != 0 {
+		t.Errorf("Found %d record(s) in test database, expecting none.", initialCount)
+	}
+
+	sourceUrl := url.LongUrl{
+		Value: "http://www.example.com",
+	}
+	_, err := strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+
+	nextCount := strategy.UseCount(storage.Service)
+	if nextCount != initialCount+1 {
+		t.Errorf("Found %d record(s) in test database, expecting %d.", nextCount, initialCount+1)
+	}
+
+	sourceUrl = url.LongUrl{
+		Value: "http://www2.example.com",
+	}
+	_, err = strategy.Alias(sourceUrl, storage.Service)
+	if err != nil {
+		t.Errorf("Failed during Alias(): %+v", err)
+	}
+
+	nextCount = strategy.UseCount(storage.Service)
+	if nextCount != initialCount+2 {
+		t.Errorf("Found %d record(s) in test database, expecting %d.", nextCount, initialCount+2)
+	}
+
+	storage.Service.DB.Exec("TRUNCATE shorturl")
+}

+ 11 - 0
url/longurl.go

@@ -1,8 +1,19 @@
 package url
 
+import "github.com/FGM/kurz/storage"
+
 type LongUrl struct {
+	Value string
 }
 
 func (l LongUrl) Domain() string {
 	return "default"
 }
+
+type LongMeta struct {
+	Url       LongUrl
+	MimeType  string
+	Language  storage.Language
+	ImagePath string
+	Origin    storage.EventInfo
+}

+ 1 - 0
url/shorturl.go

@@ -5,6 +5,7 @@ import (
 )
 
 type ShortUrl struct {
+	Id          int64
 	Value       string
 	ShortFor    LongUrl
 	Domain      string